From b5d7ce8e8c26b38a3052b3a423e4db2d23591ac0 Mon Sep 17 00:00:00 2001 From: Nate Weaver Date: Sat, 19 Oct 2019 12:31:47 -0500 Subject: [PATCH 001/237] Disable "Add Folder" button when the text field is empty --- Mac/Base.lproj/AddFolderSheet.xib | 34 +++++++++++-------- .../AddFolder/AddFolderWindowController.swift | 12 +++++-- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/Mac/Base.lproj/AddFolderSheet.xib b/Mac/Base.lproj/AddFolderSheet.xib index 169d02cdf..b56a31726 100644 --- a/Mac/Base.lproj/AddFolderSheet.xib +++ b/Mac/Base.lproj/AddFolderSheet.xib @@ -1,47 +1,51 @@ - + - + - + + - + - + - + - - + + - - + + - + + + + - - + + @@ -49,7 +53,7 @@ - + @@ -64,7 +68,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift deleted file mode 100644 index cdbda991c..000000000 --- a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// ExportOPMLWindowController.swift -// NetNewsWire -// -// Created by Maurice Parker on 5/1/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import AppKit -import Account - -class ExportOPMLWindowController: NSWindowController { - - @IBOutlet weak var accountPopUpButton: NSPopUpButton! - private weak var hostWindow: NSWindow? - - convenience init() { - self.init(windowNibName: NSNib.Name("ExportOPMLSheet")) - } - - override func windowDidLoad() { - accountPopUpButton.removeAllItems() - - let menu = NSMenu() - accountPopUpButton.menu = menu - - for oneAccount in AccountManager.shared.sortedAccounts { - - let oneMenuItem = NSMenuItem() - oneMenuItem.title = oneAccount.nameForDisplay - oneMenuItem.representedObject = oneAccount - menu.addItem(oneMenuItem) - - if oneAccount.accountID == AppDefaults.exportOPMLAccountID { - accountPopUpButton.select(oneMenuItem) - } - - } - } - - // MARK: API - - func runSheetOnWindow(_ hostWindow: NSWindow) { - - self.hostWindow = hostWindow - - if AccountManager.shared.accounts.count == 1 { - let account = AccountManager.shared.accounts.first! - exportOPML(account: account) - } else { - hostWindow.beginSheet(window!) - } - - } - - // MARK: Actions - - @IBAction func cancel(_ sender: Any) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) - } - - @IBAction func exportOPML(_ sender: Any) { - - guard let menuItem = accountPopUpButton.selectedItem else { - return - } - - let account = menuItem.representedObject as! Account - AppDefaults.exportOPMLAccountID = account.accountID - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) - exportOPML(account: account) - - } - - func exportOPML(account: Account) { - - let panel = NSSavePanel() - panel.allowedFileTypes = ["opml"] - panel.allowsOtherFileTypes = false - panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML") - panel.title = NSLocalizedString("Export OPML", comment: "Export OPML") - panel.nameFieldLabel = NSLocalizedString("Export to:", comment: "Export OPML") - panel.message = NSLocalizedString("Choose a location for the exported OPML file.", comment: "Export OPML") - panel.isExtensionHidden = false - - let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) - panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" - - panel.beginSheetModal(for: hostWindow!) { result in - if result == NSApplication.ModalResponse.OK, let url = panel.url { - DispatchQueue.main.async { - let filename = url.lastPathComponent - let opmlString = OPMLExporter.OPMLString(with: account, title: filename) - do { - try opmlString.write(to: url, atomically: true, encoding: String.Encoding.utf8) - } - catch let error as NSError { - NSApplication.shared.presentError(error) - } - } - } - } - - } - -} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index c24b1e5d4..1a9fa9f4e 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -22,9 +22,8 @@ 5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */; }; 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */; }; 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */; }; - 5144EA3D227A37AF00D19003 /* ExportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA3C227A37AF00D19003 /* ExportOPMLSheet.xib */; }; 5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */; }; - 5144EA43227A380F00D19003 /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */; }; + 5144EA43227A380F00D19003 /* ExportOPMLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA42227A380F00D19003 /* ExportOPMLController.swift */; }; 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */; }; 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; 51543685228F6753005E1CDF /* DetailAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51543684228F6753005E1CDF /* DetailAccountViewController.swift */; }; @@ -313,6 +312,8 @@ 84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; }; 84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; + B24C4F7B235D39D40000B924 /* ExportOPMLAccessoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */; }; + B24C4F85235D39F30000B924 /* ExportOPMLAccessoryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */; }; D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; }; D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; }; D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */; }; @@ -685,9 +686,8 @@ 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsAddLocal.xib; sourceTree = ""; }; 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddLocalWindowController.swift; sourceTree = ""; }; 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ImportOPMLSheet.xib; sourceTree = ""; }; - 5144EA3C227A37AF00D19003 /* ExportOPMLSheet.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExportOPMLSheet.xib; sourceTree = ""; }; 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportOPMLWindowController.swift; sourceTree = ""; }; - 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLWindowController.swift; sourceTree = ""; }; + 5144EA42227A380F00D19003 /* ExportOPMLController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLController.swift; sourceTree = ""; }; 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedbinWindowController.swift; sourceTree = ""; }; 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = ""; }; 51543684228F6753005E1CDF /* DetailAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailAccountViewController.swift; sourceTree = ""; }; @@ -925,6 +925,8 @@ 84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; }; 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; }; + B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLAccessoryViewController.swift; sourceTree = ""; }; + B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExportOPMLAccessoryView.xib; sourceTree = ""; }; B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; }; B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; }; D553737C20186C1F006D8857 /* Article+Scriptability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Article+Scriptability.swift"; sourceTree = ""; }; @@ -1035,8 +1037,9 @@ children = ( 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */, 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */, - 5144EA3C227A37AF00D19003 /* ExportOPMLSheet.xib */, - 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */, + 5144EA42227A380F00D19003 /* ExportOPMLController.swift */, + B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */, + B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */, ); path = OPML; sourceTree = ""; @@ -1989,12 +1992,12 @@ ORGANIZATIONNAME = "Ranchero Software"; TargetAttributes = { 6581C73220CED60000F4AD34 = { - DevelopmentTeam = M8L2WTLA8W; - ProvisioningStyle = Manual; + DevelopmentTeam = M72QZ9W58G; + ProvisioningStyle = Automatic; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = M8L2WTLA8W; + DevelopmentTeam = M72QZ9W58G; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -2010,8 +2013,8 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = M8L2WTLA8W; - ProvisioningStyle = Manual; + DevelopmentTeam = M72QZ9W58G; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; @@ -2020,7 +2023,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 9C84TZ7Q6Z; + DevelopmentTeam = M72QZ9W58G; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -2286,9 +2289,9 @@ 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */, 844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */, 84A3EE5F223B667F00557320 /* DefaultFeeds.opml in Resources */, + B24C4F85235D39F30000B924 /* ExportOPMLAccessoryView.xib in Resources */, 8459D0F92355794C0050076F /* NNW3OpenPanelAccessoryView.xib in Resources */, 84C9FC8222629E4800D921D6 /* Preferences.storyboard in Resources */, - 5144EA3D227A37AF00D19003 /* ExportOPMLSheet.xib in Resources */, 849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */, 848362FD2262A30800DA1D35 /* styleSheet.css in Resources */, 8483630B2262A3F000DA1D35 /* RenameSheet.xib in Resources */, @@ -2497,6 +2500,7 @@ 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */, + B24C4F7B235D39D40000B924 /* ExportOPMLAccessoryViewController.swift in Sources */, 84C9FC7B22629E1200D921D6 /* AccountsControlsBackgroundView.swift in Sources */, 84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */, 84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */, @@ -2507,7 +2511,7 @@ 849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */, 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */, 84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */, - 5144EA43227A380F00D19003 /* ExportOPMLWindowController.swift in Sources */, + 5144EA43227A380F00D19003 /* ExportOPMLController.swift in Sources */, 842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */, 84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */, D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */, From a762052264b2597c0059a89cc2af882405df0997 Mon Sep 17 00:00:00 2001 From: Nate Weaver Date: Mon, 21 Oct 2019 16:31:33 -0500 Subject: [PATCH 004/237] Don't use block-based notification observing --- Mac/MainWindow/OPML/ExportOPMLController.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Mac/MainWindow/OPML/ExportOPMLController.swift b/Mac/MainWindow/OPML/ExportOPMLController.swift index f5c671fcb..2888ff986 100644 --- a/Mac/MainWindow/OPML/ExportOPMLController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLController.swift @@ -9,7 +9,9 @@ import AppKit import Account -struct ExportOPMLController { +class ExportOPMLController { + + weak var savePanel: NSSavePanel? func runSheetOnWindow(_ hostWindow: NSWindow) { @@ -24,11 +26,11 @@ struct ExportOPMLController { panel.isExtensionHidden = false panel.accessoryView = accessoryViewController.view - let observer = NotificationCenter.default.addObserver(forName: .ExportOPMLSelectedAccountDidChange, object: nil, queue: OperationQueue.main) { notification in - self.updateNameFieldStringValueIfAppropriate(savePanel: panel, from: accessoryViewController) - } + NotificationCenter.default.addObserver(self, selector: #selector(selectedAccountDidChange(_:)), name: .ExportOPMLSelectedAccountDidChange, object: accessoryViewController) updateNameFieldStringValueIfAppropriate(savePanel: panel, from: accessoryViewController, force: true) + + savePanel = panel panel.beginSheetModal(for: hostWindow) { result in if result == NSApplication.ModalResponse.OK, let url = panel.url { @@ -45,7 +47,7 @@ struct ExportOPMLController { } } - NotificationCenter.default.removeObserver(observer) + NotificationCenter.default.removeObserver(self) } } @@ -59,4 +61,10 @@ struct ExportOPMLController { panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" } + + @objc private func selectedAccountDidChange(_ notification: Notification) { + if let savePanel = savePanel, let accessoryViewController = notification.object as? ExportOPMLAccessoryViewController { + self.updateNameFieldStringValueIfAppropriate(savePanel: savePanel, from: accessoryViewController) + } + } } From e7278017d991d986a7e6adefcce1e339abca7759 Mon Sep 17 00:00:00 2001 From: Nate Weaver Date: Mon, 21 Oct 2019 17:17:07 -0500 Subject: [PATCH 005/237] Forgo notifications entirely, and use a delegate instead --- .../OPML/ExportOPMLAccessoryViewController.swift | 11 ++++++----- Mac/MainWindow/OPML/ExportOPMLController.swift | 12 ++++-------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift index 639d88ec6..6e94a5206 100644 --- a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift @@ -12,13 +12,15 @@ import Account class ExportOPMLAccessoryViewController: NSViewController { @IBOutlet weak var accountPopUpButton: NSPopUpButton! + weak var delegate: ExportOPMLAccessoryViewControllerDelegate? var selectedAccount: Account? { accountPopUpButton.selectedItem?.representedObject as? Account } - init() { + init(delegate: ExportOPMLAccessoryViewControllerDelegate) { super.init(nibName: "ExportOPMLAccessoryView", bundle: nil) + self.delegate = delegate } // MARK: - NSViewController @@ -48,11 +50,10 @@ class ExportOPMLAccessoryViewController: NSViewController { } @IBAction func accountSelected(_ popUpButton: NSPopUpButton) { - NotificationCenter.default.post(name: .ExportOPMLSelectedAccountDidChange, object: self) + delegate!.selectedAccountDidChange(self) } } -extension Notification.Name { - static let ExportOPMLSelectedAccountDidChange = Notification.Name(rawValue: "SelectedAccountDidChange") - +protocol ExportOPMLAccessoryViewControllerDelegate: class { + func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) } diff --git a/Mac/MainWindow/OPML/ExportOPMLController.swift b/Mac/MainWindow/OPML/ExportOPMLController.swift index 2888ff986..87530df3a 100644 --- a/Mac/MainWindow/OPML/ExportOPMLController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLController.swift @@ -9,13 +9,13 @@ import AppKit import Account -class ExportOPMLController { +class ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { weak var savePanel: NSSavePanel? func runSheetOnWindow(_ hostWindow: NSWindow) { - let accessoryViewController = ExportOPMLAccessoryViewController() + let accessoryViewController = ExportOPMLAccessoryViewController(delegate: self) let panel = NSSavePanel() panel.allowedFileTypes = ["opml"] panel.allowsOtherFileTypes = false @@ -26,8 +26,6 @@ class ExportOPMLController { panel.isExtensionHidden = false panel.accessoryView = accessoryViewController.view - NotificationCenter.default.addObserver(self, selector: #selector(selectedAccountDidChange(_:)), name: .ExportOPMLSelectedAccountDidChange, object: accessoryViewController) - updateNameFieldStringValueIfAppropriate(savePanel: panel, from: accessoryViewController, force: true) savePanel = panel @@ -46,8 +44,6 @@ class ExportOPMLController { } } } - - NotificationCenter.default.removeObserver(self) } } @@ -62,8 +58,8 @@ class ExportOPMLController { } - @objc private func selectedAccountDidChange(_ notification: Notification) { - if let savePanel = savePanel, let accessoryViewController = notification.object as? ExportOPMLAccessoryViewController { + internal func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) { + if let savePanel = savePanel { self.updateNameFieldStringValueIfAppropriate(savePanel: savePanel, from: accessoryViewController) } } From 37e77a9596433859fe108ee32aa2db88eae0184c Mon Sep 17 00:00:00 2001 From: Nate Weaver Date: Mon, 21 Oct 2019 17:24:15 -0500 Subject: [PATCH 006/237] Make delegate-less init() fail since a delegate is currently required --- Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift index 6e94a5206..3c7779198 100644 --- a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift @@ -23,7 +23,9 @@ class ExportOPMLAccessoryViewController: NSViewController { self.delegate = delegate } - // MARK: - NSViewController + init() { + preconditionFailure("init() without delegate not implemented by design.") + } required init?(coder: NSCoder) { preconditionFailure("ExportOPMLAccessoryView.init(coder) not implemented by design.") From 4b1a19360c9267e3843cf90e64c630fbcc30e8be Mon Sep 17 00:00:00 2001 From: Nate Weaver Date: Tue, 22 Oct 2019 13:39:15 -0500 Subject: [PATCH 007/237] Use glyphs from SF Symbols for table row swipe actions (Thin font weight.) --- Mac/AppAssets.swift | 16 ++++++++++++ .../Timeline/TimelineViewController.swift | 8 +++--- .../Swipe Images/Contents.json | 6 +++++ .../swipeMarkRead.imageset/Contents.json | 24 ++++++++++++++++++ .../checkmark.circle.pdf | Bin 0 -> 5029 bytes .../swipeMarkStarred.imageset/Contents.json | 21 +++++++++++++++ .../swipeMarkStarred.imageset/star.fill.pdf | Bin 0 -> 4957 bytes .../swipeMarkUnread.imageset/Contents.json | 24 ++++++++++++++++++ .../swipeMarkUnread.imageset/circle.fill.pdf | Bin 0 -> 4707 bytes .../swipeMarkUnstarred.imageset/Contents.json | 21 +++++++++++++++ .../swipeMarkUnstarred.imageset/star.pdf | Bin 0 -> 5400 bytes 11 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 Mac/Resources/Assets.xcassets/Swipe Images/Contents.json create mode 100644 Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkRead.imageset/Contents.json create mode 100644 Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkRead.imageset/checkmark.circle.pdf create mode 100644 Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkStarred.imageset/Contents.json create mode 100644 Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkStarred.imageset/star.fill.pdf create mode 100644 Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnread.imageset/Contents.json create mode 100644 Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnread.imageset/circle.fill.pdf create mode 100644 Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnstarred.imageset/Contents.json create mode 100644 Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnstarred.imageset/star.pdf diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index b67f10559..aeac5c7bc 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -94,4 +94,20 @@ struct AppAssets { return RSImage(named: NSImage.smartBadgeTemplateName)! }() + static var swipeMarkReadImage: RSImage = { + return RSImage(named: "swipeMarkRead")! + }() + + static var swipeMarkUnreadImage: RSImage = { + return RSImage(named: "swipeMarkUnread")! + }() + + static var swipeMarkStarredImage: RSImage = { + return RSImage(named: "swipeMarkStarred")! + }() + + static var swipeMarkUnstarredImage: RSImage = { + return RSImage(named: "swipeMarkUnstarred")! + }() + } diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 144a9369c..422476b9f 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -779,19 +779,19 @@ extension TimelineViewController: NSTableViewDelegate { switch edge { case .leading: - let title = article.status.read ? NSLocalizedString("Mark Unread", comment: "mark unread") : NSLocalizedString("Mark Read", comment: "mark read") - let action = NSTableViewRowAction(style: .regular, title: title) { (action, row) in + let action = NSTableViewRowAction(style: .regular, title: "") { (action, row) in self.toggleArticleRead(article); tableView.rowActionsVisible = false } + action.image = article.status.read ? AppAssets.swipeMarkUnreadImage : AppAssets.swipeMarkReadImage return [action] case .trailing: - let title = article.status.starred ? NSLocalizedString("Mark Unstarred", comment: "mark unstarred") : NSLocalizedString("Mark Starred", comment: "mark starred") - let action = NSTableViewRowAction(style: .regular, title: title) { (action, row) in + let action = NSTableViewRowAction(style: .regular, title: "") { (action, row) in self.toggleArticleStarred(article); tableView.rowActionsVisible = false } + action.image = article.status.starred ? AppAssets.swipeMarkUnstarredImage : AppAssets.swipeMarkStarredImage return [action] @unknown default: diff --git a/Mac/Resources/Assets.xcassets/Swipe Images/Contents.json b/Mac/Resources/Assets.xcassets/Swipe Images/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/Swipe Images/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkRead.imageset/Contents.json b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkRead.imageset/Contents.json new file mode 100644 index 000000000..2d05b0f33 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkRead.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "checkmark.circle.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkRead.imageset/checkmark.circle.pdf b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkRead.imageset/checkmark.circle.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1239d4efc8c4a55f59acf8dc70f0a26d1aa6e0b6 GIT binary patch literal 5029 zcmai&2UJs8*M_Nrp<8Go34@^uBqX83hykQY2}nmu5}H6T2}p^eR0X7np@t?R0#X#| z2qINLX#oTTDdI?1kRl3viKG9_eDkmM-IaCkx$oU)pL6eiSJrwq*i1)H4k`}=fZOKR z=a&jMa{Jpl0B{flgmd)*XlQ^GjIqA%L=O;xCZRwIdY;}yES`4tb|GSQuxK0x3)0d8 z5QuoJiw_`>(I?r!R~*WzyX_CizRo9UZnu3HAH@`Q`i}1OH|9O*2^sN8Sq>hVMV>D_ z&xUxO?bRL5QuZRM8-)7=3;L%>43L{%-oY|l>{T~aUOLqx{$k^ePI3r5lQ`(6GzPUO zGtjdAVClG4P?amGYI0<*ORQc&|W#pL-_uH}jj&=z$zT3P_wc4sYS-g2saA#zhjKAmtw`XlcbAA| zULA<+Z=g^>f_~_68&=t&=O}M0)~<&aEWgZ_L*-4b}@g z&x#!StYK@#biy^{=F(6>%VFMcXG!)_9J+zR3=t-eAzFuB(KlP$Hh#+oZAI90^>FCl zcynl0IPZG+ItyCQ;n+N3RxE1wX5rvl8~8hZhQUk)c241>@iR#4Iy`G4e~U*r+#vS%I)+UN~?S2~B)|E??6n=ZMtnc%9%qv9crYU+zU-HMDMm zh|c#4F%;W2!9`0WtIg+zbX!DjhKtD`Gejnd>>6o`y-eBQ?1QRe~f z?{A!z>bqXMJ*Tpmf866XOZ@vVq6+Lp*@FWB`p!{8tye5XZ z@*AA-94!nY0%zgO#2#>Df0M`?p6e(fk)G>;jr{Ii;_ke1HU4df;PFqtBzV1yl-d|r4|u*>_@o#G2nifR=;HfgOF zwcx4seav>!n7et)+RSlo^jUh2{}ayP$fbI<_2j z;L0u0PPStqZ6bW%Wy7@Mq0SMu8g(r5zn##MQV6Ztuz0Pv^|gmvP;4|-r6*-AOfZG{ zME9dJ*`vGc11&P#eA^k8RT9OW{GO>OO_3)+9i;kMd@p+O@e?NAL%8TLTCHdnaqH=^_;%}-Iin@#vFC`rH56t6=$;u?NQsJU0?)yNIO)#^;70b?!5QZzj zTLQ%%2i|)-$zr*|sL~>B%S&IU@M{lmP|ds`&Zw+Jidv-bp~q|)WCf{+D?AKLc(5+Y zJPMNYGO9*|FNqUR*5uPt)T*%uH$xx#{S`6e!f?rC{4BG4JudFgA3-7(G`sY@Q)G&b*B*t%6Yl5VZ%l7SIxDV7F*h^lF*{e}t~{V;jYkD6F%q zaTM7($A$bx&0DsbqolQ|J>&7v8`fpv0Jf!;-N(OI)TrDjnYOFt^NloZnY^qd8s9lr z`I(cXbulh=rsUJBK(31k%Cm)^jRGa7ZvWmA&3;Des9J>d%$eGwj5^1X&fY(Ya)p{} zTk;`vm>)5&v~k_b6a-BrTB4FMceo+3c^Iab;(sK^z9hf5!UMlVPIQIJ6a%HR3gTj!}eMd{Q?Dw(=M4X0XAFHxhZ zV#2o{m{#V0?Z4;YV5e0|sgATVRX1obSt@vDWo892_p@TR(l_Uoy(?oWy=<+L=jS++ z^rGTk#=U}jg|2shb9$_QxoTFloxfc+=2=Xu;iBP)p_bui!*s)*yGpVMsVs~7!Y>ZC zwqwR)NL4-C`;C@CmFr^j8{L}SI7_&t_XQ?P)CG)8iRD%?1yz^7PBAI8AJ|)v{zBT7 z2|l^UxyQJNKqk+0O+$9#z;RxB;&W}uHZsq(JdhBBJ$we26HgAt$OSOHtRy@aVjYp0Tk9Srz__LpsJsEFO{n{(5Rn1Wi z=YN|xR?jqfyq>Fm)Zc00{NTpu(T^8Co}?~OLrVInJcE6MP9>qn2ThEg&X;)670;cc zTU5hu7}zB@N_>{MV63LGAc=h2KLQ!?%bW3SV;fKMTh(_GA5FF&7^G}1M?Vw5*8(G$>PZ8c(LVAWF^Z@X!`WJ`DyTgqee zt}<_8XRNoT=y9EDy4u)P{K)&2LfKyCJCRrv0 zHcu8Xiw{>YuQxx1j|2*Yw1F`$T}5Vw9)Tan`|U-%H3{9lx-bRsiBqw6GEpAVx1_72 zk(FU?K9u3i#XEM{@+QIBN=#iHMvRtnL?{1`l?=Yzo`vagqQVZbxbxk%5AlmL_SWg zQeHv^ptf2>A}LQPZjYl>j7UnODm9Spj5ZUL{6hAcYUtc|ZO_`LN<2N8S)+H$Sr1y4 z6UVx=Qa6w7@LLKrUQDc=*3S#htF6fIs`k(w$e*2_Ef=`M5yUZJXl#^1{&Lz?VZ2Jv zUITY|;>ZRVYZROsb$YVK*wc383DC&4V9C&ll=8*aDBXN)@JVz|H0fF{#av#lx$5nu zudfxl6m*;%E<*j=Q;x66?i?48wIyF5uUV~IUHm?{(DTaanhm*WaouZlSbfB7^yVnB zbkn{8m$h3z>(4XVXY;Q1MqQq3YwT@;^2LW4R#k-LL5Z;ktFQPnGplX9Z7b&|t!74E zFD?wT)d>08KVBIQ*xy=G|9%C&3?H|x+gP!_SMIw87#p@hU$CgktZ#7ME)O}f{WNrC z-`|n;Z-HIO326^$*lu4{xBtV+y5^nV!6Nex)ue<2A?6;t@o8OagPNCtVZ{uu0!9sE8aLT2rQ4@#lhHJZe z)3<@Xc;AbQam$S(*D@Ct{)~;t=eC@ihKL+ zY4iEuZytdy>f;TsLQVE7=dbR33jyM_NIS}Mj-N#S4V85Aq5~ZarlkA}(CExY*Jxm8 zfJB~kAz(2eI<%ocj=w_aWciB&{{m0K|3pbTBl^?Gl*V~Ro)`j+9q9;5Py81jLg^6t z&*VrKqKh}q{ZG6l{N&od&4B)#A?4$OHnjj*Vete{oG%Cpk%z+Nl|T+sXZ=0BF(9bQ zIVc3?APqA2cfk{bK{Rarfx>}A>0d7f{e{$0I%5JlQ+{g;M7(CGiaG$j=p;Qv#D zE5iS^21NPabK#1L|B8ddlxem6uY7RD|3_1$H8+065%Dgb-dOyP-j{`EFzx%nKnf@v zj^-M=GiV>uz}F2&v(TSo9h!+Wl$4d>P>idKG6I89b-^k_F^WoxZcrCDHz*8>fnc-% d|2u@%!Jw6dK%`mYM?v69P(=V3tZS+V_zyT7xHkX* literal 0 HcmV?d00001 diff --git a/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkStarred.imageset/Contents.json b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkStarred.imageset/Contents.json new file mode 100644 index 000000000..edee428f9 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkStarred.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "star.fill.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkStarred.imageset/star.fill.pdf b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkStarred.imageset/star.fill.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4fb2ca8ce5fe3242f3ffd1d1da3960a2b50d1a64 GIT binary patch literal 4957 zcmai&2{e@Z|Hth#_7;1LtFa`^FvF0PHTyDTmt~ASqnWW3A=!#7$v(Cw$riFNk?e+S zgAiFOdm*Io8{PWf?!EtW?sMim&-ZyhpYP|peBNiyd3`~KY8ny{Nf`jBbzx&+Id3z& zzqK6z1%iQS2RDGCB2Y>f<>`!b0m8@<0w|^7>VZRH$WIS@97+v^L_4B@N=g7M4ui74 z2JoZQLukeDL73`C#kZ*1#?LL{IeT~k44996bo)U-lRn%qpWcwP`bL7%$ArNBKtP6p z<(8sW^tE&jkpJ2n?L$qxnHAkNPj^<{$3xY@l?BB8IOdq1@~c*N^atlH#iP6yEONGh z?lHruoU6)>ekGr7H(ss{ehAv}eOB~5$VK6pp}!mA^YMxN`Sw2~SRQLUW^!I%QFJVO zW=?yB?%<b;y@YJM+1Q&;#c`ZU(JZUc z8T;LxLwM@{M9ifeMi z3k8%iw0Hh-$DllMK$$-ajX+`1-WVhb3zYp+P(^#<$ltNRBXf|QpoemFwO2v=0j+p=Jgk}q&;}@_iuOQbjJ@oU zDBzLbRIw1C+>arYl#ZLlp4y%6^XixvA^-( zcFi@TH%}PlQ99IorTxZO<4vb@_6m0@r6a4diNkSCECL`l+5OwKJ4$`MhkOS+0nP!-7v2R?T(ap*)O>!O;!+IU zmghK8KQk5o>hUBs;1cIks>|8k3kzCr1_s!@0Bj%b(SE$}dFMH!ohGJ*aus%crD|lz=M5PG6#q&uHjSZx7OZ4h-q7Ls2y9^v|61$QF>j(O1WD^^TYP@!RLbsDY z*WPBRR}Dz%V7|xO7R#u1H=gYqb#S`YMqY;sAKhm~3sWjy2mi?Bq1@(UY~NG@tV9{r z{dg!s^~=CY#~hH6Ev=h>5P;jE=AA@F&D(G2W_fag?`+T@HLOoAU}pux_agHK-BRo7kNie@}#zE zJl~$q1;MVQO{VtHJ^iy$>W!)buFX`}sqP8RwjrKDMQI$k#4W`&TWc;+V44+olj&Qt z>A}kUY`3Z8yD6;WsCiFV+Ob+O;6dSf0O$GJ=S2I0Yj)<~ON3J{_i1A0LvU~z-e@ z;5yM!F*7k%Q8~&JjX^m$#2&Ctr*`tB~K$r&!UO61+T|)<>-DBur|_=1v*9N@;PHK;kstlaiPi&tz#B@zW56Y&Aj< z{v(1T6z;5rnP2cuU5G!Q9^v|R|1}=Z~r=f_Z*Bv0vdGAIF%sct+-=&IS_vw53D6KR z4UJ#vG&tRYpJ~m8cyLQH$xR@r zlnXE{=E6PfYcF2;oz*U@M=EF1flox<1!f*`k$7=*K}Q2DEg4| zzJj`WcSI66qB}LW9#Me&vIeR4J{7vLG>x$Ii_1;ZHl-?mVqv45RhCYn-e-HIt8MS7 z^HSQ7T`@}a29^zGug)NP3X=OuiXjgcA=$D)K z$}W~x!*3T(Th_39h8Z+ZUYF&M>6k13%oLz>H9C2w@KdiJ^VL|n*}Ts>ej-!%FE&Ro zToOH@5GpovspbTw+R1p8$0raD5F=$1c9`lE;Nu5zRcwf)WPZc^-_=&woTh#2{Wb115EDXyqcj z?E>H0O+Cm@C54d^NvotgBx900DS{-(bN`7!IpJ&nBNuB+rDA+ln5n^et$O|C+!v;X zreGs4QwCE_BR27eXHCRb%-}g*HZ$?BN*|>?%6*jQ@bC}YGR^Civ;1uwZQ}P{+-uQZ z(jL)P(*CTSs!e<-D-ILQFs{qnvbL}o(;ZWl*RXipU~;2;LvUfU`$9L`1Zv`Og~|kR z#qn&R$#wxAQA^ms>*rYw>@SLK1?-)@d3v8|pK>3jnlRHj4c?6gMZ0MT%_+>~7d$G+ zJ!|D1<5uRDxWqmzUO?@3&&|}0(#^WE&28AN*R7fqPJ#_*kO(9!$*f@AtSNgcPkE?x zkYRAz{h{m12%PZQFrgnh%G5va8u+C4@+RJ?gyRa)GK!Fxe3Ql{Pj63okFv4q zkfCwvqT~?qNsH5n(=n%9C;HyCbn(>s^t^O57%d}E0WM3iH*&cCa#ln~UB_1^XF-_g zLo_oTG1W397ROk8u~@di7DW}an!hX0nb;lcsm?E}l}}X|yM-BrpMNKrDteYcOjihVFLiF-G?lzZ9vZB#|{$;yBvyOLqrCM>I3Q&RY6X6k1qc%X- zGMRJ%+ufrsBL(6;6ZIe+;UacdtWr$1JjCf5emH&Ufo0}6Y~rTR%nBox>Ahp0ypox> zr0-fhRNE3Ueo*22R@9T(*M;ACs_Mp_28t=+sS}MCgQ%Nqr-jT#{2s0)G)lgZY7s%+ z(rD^8FbDCm(MDF?%S44ct@Q*Xo=U8gTUPZ!Y&Y|T;VbY?W#Mq00NGJ^HFzhjRUaX_ zn7J+=H1}QEwWdOrm6)C}`mluduz4kJtWzoZ%gJ316V8UKaW&JLIX81^N(r4+F6sk> z+3DF5&IgP)7$>xKb&?Xd&N)bpS8`b?qOVUJ-vps_ZYGDHo2=G#wHSFWpktA{tZf^R zv}K`_YBVONNP>eDpS zN^HWQ@Ys{JUiS3#DsvBu@`Xv$nUU8^i^KHQ+@4lttHVAA+so&_`$AWs;}*4>t7eZ% zJl6qZ!{*2<#+B)H^>#ZY{>OJJf>sZ_ZOH!?sMRe0HvjsawzczCA56`v-+3J_QSVZY z-9FeO?G?VG2^=C#_m?hbzi#eUosL`SpF)r-|GT4K$yR#9fef%Vf(?0T9#91nf@uKfR#I(Tcit-7A%pLi6H=8$AHr02cIwWhax~+IE!uJTo*vnG& zS3MhMeR3BII);AabGof%N$Y~%QT0ev{D+X3vWEq%RBee4d+Htsv$&gNu9Lxe$K*S7^5^jOK3Z6SZE zV!<+Y&3%=1t!dA1WNu32fk+GWLR0=>)oOn4(0bs`;R~eKd%Ja+#d6l5qJU?I0qaq& zQsd!|BKRA6ZpeQxn5{fmzJ-4KuA)ir<~J9==JVtAy+Qg1#Qrx+9zmiv znN7){r{n5~CBx$p#2#h-3lJek82b0@s`fa0547{2fQ$V}w!ig&{Olom%^qoB3^YYy zu&!uNAOtK4flA5(twmM5T|FFu5cp*XSjJimXyk2=!QBLsx%CGO`{BfXtqk%Dszucl zq`?q*2v}NL8Vr+{k+uYbMaaLuB7d7DnS~v_k>ukP$k9y5Osz^jMZuA$pmqeZKXM*{ zHrny`_rK=vhe0_3z(9x$0Q`R!P!KQ$;+mV5+obOAkoX<(>4xqyGypyb*9 zQv;J1@h=SuC-3v$`+{X5e5Fh9<$j9qV%_fG~Wg+Qapt~qiBc_XzvozP?p{n=I{n@G_S4wr+;qsV(E=O8C1 v4TVWN*h_<${vQ4Qv&>V2>A$uToNpfY>giUfx@6*00^XRpaJ+F&f%d6 literal 0 HcmV?d00001 diff --git a/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnread.imageset/Contents.json b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnread.imageset/Contents.json new file mode 100644 index 000000000..2c571d7a6 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnread.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "circle.fill.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnread.imageset/circle.fill.pdf b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnread.imageset/circle.fill.pdf new file mode 100644 index 0000000000000000000000000000000000000000..66a3fc9ea45e622006b5f879d29cf6bbf49279c2 GIT binary patch literal 4707 zcmai&cT`j9)5oczpg!2fOMpU&?T4zM2Mi$RFEQu8k!Li5CQ2$ z5UEn6g&?3vb)_pPAPW2vSKr-z{bTRR$-VjBdFGjCW=_ue49HAJPXVF`1%O)T*5(%T z*K_(?+W{~j7>ITC2B@n8m5ecd9t2MyoGzh&N_t+t1PqRT_jMs)bTDYF8wRMM0l*V* z7?+EHAZAS8NdPN^i>*J@6e44GV$ow5*__qPH~7GY7}hCW8VFq0?uq4DQuVLr)sv7F zIy$V6Iyc!G*(-k`0q7^AXpwKp&mjtj-nnSVjg!CoAywKS*nQ1#@Yt)zU&aMdTq(Xs zPq+=#n1q{I#Qb4nkn;U=Z&B?+(!KeHsk>Q^{RR7`QXJwE-tdvZE5TKGKqdI={(g2lo zFo9lZ%vqevmH)0mgwvSacr`6u59$j^?z?fVMz#$= zQ{WLhqHr5kVaG44NS)5{Gh3`NGh)vZD5@z9?mLjvzfrrX(bu~tw!2C6ATFMI7shnP zv6H0#^b*sVI8`T+gWV=sY2+6V-m(JD2tHyto6|KnXYhJpfZrd$_vdZ)52rqDKIL@Q z$F(po!!Ip0ZO9Te#d%+;vD@>Xj!$7_de6;N950jjo;AmldG0;u8fbsC)??BU2~nm? z$C&`fDL%PEsS*2ISqg4;a3$hmQYGJz#T17|2hxNUdd~@B_QHof2lm#xUnWb|5A?Ai zNDYr`{I>!lHc~#-Uf*A@O-$|JzAf4o}Ok?$MpwG4x`{0=R#eV^2Atyn}|LvAh( zFnmzzy5mPv`Fr?@HIBHp2Oie z{Iq1$_RajkH#V?$0!)J$O8dD)5|id_Yo|K*L1p(8OMpBt=MaLNtO=QBf}^CD+%ZwF z0n>svmiS;2E838R*|U^WF4+fVU&ZMJ??^s5IQw!Z@~WYABUpT{N0_P5wh<;?99d;P zGo;%rb~9X3@vxzGqS&_4Dan_~>s;*-+a||jbQ`rLyqZ}qvD}uNZbLnR$+Ec$$=k{6 zx7M6t!ZoXJrEs+7aDX+%`L44dyOam!B`31 zpveA4vDduUP{Lx}*Mb@ZJUXR3_!O!GTKB=?Dn7@1zl@aM!Z}JUCZtGJv7gCk%Mpr` z9G}ffsyh}#24n~dYi=i;s71(;&Dq3eaoAd4<3*XiMC^`4<*VqbxXn;vL%S5G<98T}+uI!||H9+J|n0YA~2$nz+^xspH zxj2ty>zRwQbxS$v-9UxVBxy`OyhT}G$X_OhxM&JA>|+y}qUu_Njj0$Y?oh7|=KynR z>4+9tI(_a@mS<#NwZzCdx2;ZOdZ&M@%7iQ>G5#xsEYi1gNxW+YE}#GlJ7JMVkqJ=W zG$VFE_hFX8$~}pCGFB0ZOP!`iTgX$bIS^lAMJ@y@I`u_I+7xdhvL%+kxj&MU%jKg7 zL=ScZ#gZ?%aj31{bU_D9SsXEnqM(h>>>iNheXy3^cJ#|;iH5RF(5>U?p5F^zeE8@V z=P6Qf0Ik+BecYonB8kKVzxABOGx~7*k<38IV^GflyctOE{C?$w8KAzY(`2^6RKg#~ zQe|Cc>6g-u`g`WFg(MZCS+PjL1kVEru`eK2gUgm3?IBE;C2k28dhYq|Y$ZwN1fz-z zG%bC;PT*D_U8kCPgPl>C31qcMk$q(x=_DoDh|9c8EI5!Z$~+33{W7Xrj5$f_J#q}1 zX>0OvF>1xwlZUAfJ$qTwI6qt_Ng%dU|MPt&P*C!CV#aYzS z*p^;4^qPH1B#>jVdAsa%X|?M0qDi|Ne!oc5=C_v+;&B}_<)65S8W&`x1)NrZ=)rT5Ql@z)2(6pTLwg0}SgPle( zxhm4i^rS(($zt9!D>EywxxdwZD}8f5`FqDL<(8~fbNwBs5?_?wPrsjcKi~D0t zZoTVN7uFJH>3g2V5_R6~c#-8sAsJOmStFa|+Yju_%Y7zp9}hmd!?nY_1J_QP>YM~` z#e!nJ^`vIhW(o@L7v>$e4~X+F^Cm6u56c&_df)c8@@DpSsBH5d_U`qrrbbcW!naI~1DjnQEIO%iGYl)^x`DB*V4;$g?pY;lTSbKJz>|P>pzS}N_LMk|4 z@Z}B-Q>=NBd)aI3)>!1&@K{G>eZc;s@<-!Ls$O|#wx~I(VFT_EM(bGK9;xH58wqfl zKR38Oa_GbP4@aqs)R3Y+D(_(5pi@zp@m?ddm$MHay7akobTcyix`AC%gY+lq^TulG z^D^3R`e|UAf9{lD3r~xr6q>>(b!$L?0@Y(}S8LyGPiq&AH5J;-@J!!IziEs}CBv@t zP~D%VjrR2Rl=o;_Xb+iLq|GZ1bw6xzr@0?+&vWPKUCEG7tIx>Kz=E+*i3)I8s*Abn zrRUQ!M!H6qjdJIty92wet!P#TR^7#MwqI-)ZSjv|ig|6`mFJFcjrLR*l+_~B)JCu3 zXtT>nC1cZ7id9aHoNz4PDo9Fy1O& zCF@Dn*R1GMVVk&*U29{j!+%~}R`@tEv(w+v6TMj8oj;y3pYi@130s|AATd(XzUmQ% z4{Ho7-jmvJ3zV+U+99a<)DgA^=1W-ASq0e1fMYD$Eb=T$9A0c7wu{`se7*u?ej+dk z+zN7Y=`1ia^bGni)^9H+#S?GQAwJ^s!o|e{rukS)}(wInz8P9cdpbR z0aO7hlzu2p12q}~jO|jWr|?}px=ZcZGo=<8^i{p4ep4He7$5u1s@qwZDEE~fBIyXJQe{y)5Vg@P7D=ukyO%|& z8W9mAs@34l^i~s;;(XRBGHm9%rdLe`g10*(bL3tL`(E=>!f2;P%9q1i0+xae7ZPeF z^>c%BYf34dRi3&7lWk{O$^N;n=2|bRlf20`bw!&Nyo|I0wka<`N*pL z))7H@The*bs@0m+h3|v&-MvnsHl)UdHSdw(lQgrDnCM^N{>EV>E+MJsIu|3EuVX9HAQ>1Fh9&uE$nAswmclTyRmrk z`(@Y?Y|OTHecAeciQg(`McDFgfFu3i0<)YQ(iT#`*|u`h z{!c6G>UaKo3#?nrqt|!0soO>G*g}V>ll`TOIj@?#v?mjm`e(31jmv6cp*71lza>Vk zo@g2mv|RVpm7>{DRfCL!9@op&&fDQ_@b)p*-2M!^;Ob6!87j_IM`%H|OIBeF3ZtG3nk?_O$U08@+YM_z&1HM@; z6t9ebAHRC}>8kp-g30`;dZQ^&<8M%pPh=t)6@$W@hd*dyem!HLA03(nY%JAtaD8TdsWK?y+f;^n|sgD{_mYv zq4VXuVULMV_K2%7UP@z8_oKxddafY97fx61E?&jHc~{YNF8G^gQ1i*L`ra^;-SWAs zTi-$?a2mud6$QtSV!uNrgS;3(2ZbV3egPVT*%%rf>rKnZ5M)zFV^EPyv6_I+JCHo z{9GY>(FJX40kp#4@LpIyAOx%kfhi(@4zgMSUcPQXi0WAg80sJgG!JmW5rTnqZ2f`4 zK?J#92ZQ`VYFQmMWiSK@0V_ixNM)ol*bWSqp@06i{6~~@6m|88iG(!!5~#(2o*F~)g6g&!Ds;f+k`&BpqB(sptr`4g20rKU;qfD IYpMtMADV0&i~s-t literal 0 HcmV?d00001 diff --git a/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnstarred.imageset/Contents.json b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnstarred.imageset/Contents.json new file mode 100644 index 000000000..7b2919e99 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnstarred.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "star.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnstarred.imageset/star.pdf b/Mac/Resources/Assets.xcassets/Swipe Images/swipeMarkUnstarred.imageset/star.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d1f245bd7fd2fb1355a444303f628e905f3f62ef GIT binary patch literal 5400 zcmai&c{r497r?C%vXqF#NMkK#n865T-?y=Eg)#OFGh-jAWQ#0`EMseuY$5v+$(nr` zBb1%87ljDl=^xA^E2Y_vd9F);9K_LuzfqVd*t;;fmP zGLIuN@evr&lM+!;P+YBMu8!NzZ>Q{s`n&K*H@!vR|3oy)#mC#FOC!(u&c7gUSgT1;35sY ziGGPu)^3iayO>KWj6%-0rZuKJ%02(ayZRn1!Vtwzr0{k@l;WaaV`3L5I)W|XE@MDcu4IQw*r zv8iK@Wh&Q`Mj~ya_in~Fd(p&)Wr=z}1z`!so9671-7Y-u%kiy4Zsk8Gr=7K>&n2}fR4(w?ChP72ru4ud~MKg~rE!3UtEinG!19P7$dO&Yx1(H>Hu4SgbK<7g*NuGM<4;mr9uS*2FUzcLQ(O!D*Uz~ z`!^fEs|oCvmV`b443JPdt|*@5asrT0Mf$oTkQ!+Ffd5}FeRX)Q}{pNdBlgu z22;7N^y+aUk3vUW29LJ7-zD?c5BAeYCp8q+cs^E#AyPJETOG^0B25EQvGoZEbobzd20EzGFq&rNY(zYL^!(g6|K(z(YdH4N^S?-)Wp_?=absP2t%q)NSUS!_T2Mfp)2gOjj1U(aXLgJET?M&wh zDd8jW5&-kN1!+!3>i7&p&atF-r=r8(Gt6?{TV;jtt*e0J=QRil_F1Qe`(jlC5BOi6 zo_}``7OZXF2;yDn;i4$8Y=rO@hgBKP4XZWtM27N%%Y?Ho4{2iO?_p)7&c8gLC`rlplFu??yEE+6_kqUX z&ENpS9#2jq#aziy>Js))SD} zif?gl@501((Y69B@lOP*Xs@TYWuJ@XpP0`~th*GAXGrJdQre5ZTq`YtH=^O0N26*z zbXSD>6Ho_ym3_PBN-7OO4h@eF9)o-t&Bd-PnV$E8OF zreHj*5*?ke(F0$yVcY0hZa#{yG#KL^UI+1`jOGIPof(c$4R`8_M$OY(K^BDaL~B?pw;&(kxs&uP7`LQoUu|r@sB8oB167 zXtZp1^2WV$$<*h&imqplvcAY-+)AJ$TnUv|``~pahAXR{?aG|~^&qW6UOV4nc^^v9 z%1By&cV#Exqn32;q@k;X7H0`KUUQL{%*1#pOWlZ%1jCq z65@Uk@Z9|e-n?CNP>w5*kjuuY1R)>!9Yb7))CuHjNVyA6UC8Wy!fL0%g%u(5N?5E)neQXES`k zMZlhu7(<}CDZS+BbYTC?H9XBw3icwbR8oyn@SUKo=8;iML6Qv4u$Vi4lKB}Q&vziT zmY+#Sdk}>mUleD7%aO;yZX$nnAiOwV$;AD~Bzoh;U812I$PS(nkCzMMK2b)OmLws3 z-;afY3Jp|)8-;_i-i255P$mj2!p5aCEcL&vgsA3lePL#$omG-dp*~=JtE+AAsPjhBkXf{uyOhm?_G&Y) zAszP{doQMnd&ARoUMFp6Gp5CVW`4UTWXIY-({e6+5G40JY6&5j0(6RiA=XnQIPR<2 zm03t8ha>GAbi?f^DYGGT+NQWsIKwVryKehx4hZvszre8dcj&T*uAC-5mT`d!kz8JcfQGZTo4 zSC|L<$W+cd2VWJ=WM@VS0z+Tjf$Qf#CBkFjea#FXbpo_<;XZb^KiEw^%1 zkyUfq9536Mgtw(n)1Kx&&2xBs@n)H(cjYW^8%LYigVzsQw3oF>+KSp=wNtgbA4`it zg)@xn^1j(vT8`vgLph7+M98AJjRLo_egFmKA9%u^aJ9iktac7N=;N|GgfHB20U zj4}<(yWW0YtFeW5D&a8gwu&Ys<(uYze@H?WE8RM^<~klV9yUHQ-cecaLw`Z+LacsO zpIb(YoUI(n=OK2ij_Tvtx>I$dJ~x;2hPFn}d^Y`jfw)2pD(ok+4D}D)EDX^-YNT|v zb7w`AzOjpFhK1hMvPx_a{3>XwD<{7sr1D{a1R{Cm%y_mix9|%f2&@88gB%1Yb?SDx z_QUqn_VY0Y=XTOv(st7#b){4AkboYd)2r06p5C7F9wlRyVMF88CGp|z=Pgbor;|>( zPISHN>0+t%>3QiW5K4-#0#ue_Z{*!c?O~+3sXF;&rx7*x|WTs`-T^wuq-Ezee zQxsjyVlh>oGqF3?Q=MN{3rm$73r3UX*Ah#{XRE}klqcLK#C&Tq3Nt!A>pwz+9=wit z{l2mNFgf%A^+e8_nL8?RgjmFVME)GOBhUEq^Jlu2S962Af|41<&fosHF6Sy621}Bw zls%a7Sn0vM^lgMCDMJ01ALZ*AXX%xv3#L>d#&2=zJ>e4XwjH-Q#QI>8>ZEJ7>n&?? zYj0ud7hW##t~CxFG|;sn>N-xxnZ?OuzRdiQ8KDregZ|RBIleLS>DJnnFOzcz106jP zE9KpJ6Hk`X7x$A;)miy`qb2Pd&LNnP#*pG8fo(@$!TQVttekrtc5iU0gi4;8gBH(l ziAse^j7oyel?F(2>r^1C2M3-F2k-~A0v+u;^9{9K{6CKmSn~)l#~F8UF<}_3*?pyL zfb!xv_uYQBDz>4>WUk$qQmz%}578Ad>>9k$R7Ut0-;H(%#e`29A$%$iqhj-8e?Unh zRhOz1^rbvKKOELP`8qsj-Z7XJVkEAa@vdSEXP6YbStTJMl79IOxisnAze(MPkZ7cZrVTIoZVb4>3 zF1*f@RRN(56qACJXBw}CP&eJ27O)WVf4rX9DE?ZaMFDdy_M~nfC6WY2u$w}WX zJ4lRIp0k!mc~6|)0wQ$+pM+ojSgq@7NqWVnW0||6eG`}b%~B`TXl&?JL{v~kiUfrw@%V@vFRL$Mm9EXxucKw^UCOb$hkL}m!YP{D!^G)<(v|GK z<}Q`#_|<_q)NtdP9MA2VwaER1@Quq&6TT)}E@}cK3!<#Qu76RzNbQmp#sXsn)aQbET^viszyzRgbk z?LGDBhkTxB&l}4zs|}>20iS)d<>K{;pA*4;uQufO^QZG>>UCy-jkAs82L*KDEN+L; zA2;dhZR_K9XtuFGd3LrwMl?hi+0}PU~5dj$a*rTz505Z4!f?U>F!ADJcnp!lb0FKp-LV z{g>w7yd<-*qYr}I2?HN{LS||easv!Yc0u(RWPjN_25pq%-~a#Rzdsu3!~g<-r5Hf} zy#Ufss1y|71o$n3z(C|fIKBX$f672G2-)3#${^B^f9sJy;(zr(FiG+${&OxEMCShg zkwK)v|JDObl6wdLoC_iE(Z6Ie5b}QfkDd(d-!fV0zpaZ!+q-%o(ZAY_#;$?n{gVPn zz)>i2)Eq|!c_XzvolxWu`eUq04kCF;Nhv54DhVZ@6-)*s1%WzB%1A@(?PZ+oouE!& hX+?(ruR?BNkdFj|C5OhZ1A)L~$&FB;nt?jQe*jTqe;)t< literal 0 HcmV?d00001 From 06b0e35739ec10196381f5c8d596864e30c1b57a Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 23 Oct 2019 22:00:14 -0700 Subject: [PATCH 008/237] Move Protocol declaration to top of ExportOPMLAccessoryViewController.swift. As critical API, it needs to be easy to spot. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create separation extensions — one for protocol conformance, one for private methods — ExportOPMLController. Make minor code formatting change: instead of { return } — which is hard to set a breakpoint on — move the return to its own line. Remove an internal access qualifier, since internal is default and implied. --- .../ExportOPMLAccessoryViewController.swift | 7 ++-- .../OPML/ExportOPMLController.swift | 37 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift index 3c7779198..d558d7da0 100644 --- a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift @@ -9,6 +9,10 @@ import AppKit import Account +protocol ExportOPMLAccessoryViewControllerDelegate: class { + func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) +} + class ExportOPMLAccessoryViewController: NSViewController { @IBOutlet weak var accountPopUpButton: NSPopUpButton! @@ -56,6 +60,3 @@ class ExportOPMLAccessoryViewController: NSViewController { } } -protocol ExportOPMLAccessoryViewControllerDelegate: class { - func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) -} diff --git a/Mac/MainWindow/OPML/ExportOPMLController.swift b/Mac/MainWindow/OPML/ExportOPMLController.swift index 87530df3a..eb3fae3b0 100644 --- a/Mac/MainWindow/OPML/ExportOPMLController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLController.swift @@ -9,7 +9,7 @@ import AppKit import Account -class ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { +class ExportOPMLController { weak var savePanel: NSSavePanel? @@ -33,7 +33,9 @@ class ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { panel.beginSheetModal(for: hostWindow) { result in if result == NSApplication.ModalResponse.OK, let url = panel.url { DispatchQueue.main.async { - guard let account = accessoryViewController.selectedAccount else { return } + guard let account = accessoryViewController.selectedAccount else { + return + } let filename = url.lastPathComponent let opmlString = OPMLExporter.OPMLString(with: account, title: filename) do { @@ -45,22 +47,31 @@ class ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { } } } - } +} - private func updateNameFieldStringValueIfAppropriate(savePanel panel: NSSavePanel, from accessoryViewController: ExportOPMLAccessoryViewController, force: Bool = false) { +extension ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { - if !force && !panel.nameFieldStringValue.hasPrefix("Subscriptions-") { return } - - guard let account = accessoryViewController.selectedAccount else { return } - let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) - panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" - - } - - internal func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) { + func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) { if let savePanel = savePanel { self.updateNameFieldStringValueIfAppropriate(savePanel: savePanel, from: accessoryViewController) } } } + +private extension ExportOPMLController { + + func updateNameFieldStringValueIfAppropriate(savePanel panel: NSSavePanel, from accessoryViewController: ExportOPMLAccessoryViewController, force: Bool = false) { + + if !force && !panel.nameFieldStringValue.hasPrefix("Subscriptions-") { + return + } + + guard let account = accessoryViewController.selectedAccount else { + return + } + let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) + panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" + + } +} From 6f29497ec89daedebecf76bb6569e06e66143257 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 23 Oct 2019 22:27:08 -0700 Subject: [PATCH 009/237] Move NSTextFieldDelegate conformance to separate extension. Handle the optional nil case in controlTextDidChange. --- .../AddFolder/AddFolderWindowController.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift index 5cec5174e..2a0153582 100644 --- a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift +++ b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift @@ -10,7 +10,7 @@ import AppKit import Articles import Account -class AddFolderWindowController : NSWindowController, NSTextFieldDelegate { +class AddFolderWindowController : NSWindowController { @IBOutlet var folderNameTextField: NSTextField! @IBOutlet var accountPopupButton: NSPopUpButton! @@ -79,20 +79,23 @@ class AddFolderWindowController : NSWindowController, NSTextFieldDelegate { // MARK: Actions @IBAction func cancel(_ sender: Any?) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) } @IBAction func addFolder(_ sender: Any?) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) } +} - // MARK: Text Field Delegate +// MARK: Text Field Delegate + +extension AddFolderWindowController: NSTextFieldDelegate { func controlTextDidChange(_ obj: Notification) { - if let value = (obj.object as? NSTextField)?.stringValue { - addFolderButton.isEnabled = !value.isEmpty + guard let folderName = (obj.object as? NSTextField)?.stringValue else { + addFolderButton.isEnabled = false + return } + addFolderButton.isEnabled = !folderName.isEmpty } } From 1c06eb27e8d197131647bcd876b8de4eb3178d11 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 23 Oct 2019 22:33:21 -0700 Subject: [PATCH 010/237] Create separate private extension. Make misc. code formatting changes to match current thinking. --- .../AddFolder/AddFolderWindowController.swift | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift index 2a0153582..c572ce1b3 100644 --- a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift +++ b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift @@ -15,17 +15,15 @@ class AddFolderWindowController : NSWindowController { @IBOutlet var folderNameTextField: NSTextField! @IBOutlet var accountPopupButton: NSPopUpButton! @IBOutlet var addFolderButton: NSButton! - var hostWindow: NSWindow? + private var hostWindow: NSWindow? convenience init() { - self.init(windowNibName: NSNib.Name("AddFolderSheet")) } - // MARK: API + // MARK: - API func runSheetOnWindow(_ w: NSWindow) { - hostWindow = w hostWindow!.beginSheet(window!) { (returnCode: NSApplication.ModalResponse) -> Void in @@ -35,7 +33,7 @@ class AddFolderWindowController : NSWindowController { } } - // MARK: NSViewController + // MARK: - NSViewController override func windowDidLoad() { let preferredAccountID = AppDefaults.addFolderAccountID @@ -54,40 +52,21 @@ class AddFolderWindowController : NSWindowController { if oneAccount.accountID == preferredAccountID { accountPopupButton.select(oneMenuItem) } - } } - // MARK: Private - - private func addFolderIfNeeded() { - guard let menuItem = accountPopupButton.selectedItem else { - return - } - - let account = menuItem.representedObject as! Account - AppDefaults.addFolderAccountID = account.accountID - - let folderName = self.folderNameTextField.stringValue - if folderName.isEmpty { - return - } - - account.ensureFolder(with: folderName) - } - - // MARK: Actions + // MARK: - Actions @IBAction func cancel(_ sender: Any?) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) + hostWindow!.endSheet(window!, returnCode: .cancel) } @IBAction func addFolder(_ sender: Any?) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) + hostWindow!.endSheet(window!, returnCode: .OK) } } -// MARK: Text Field Delegate +// MARK: - Text Field Delegate extension AddFolderWindowController: NSTextFieldDelegate { @@ -99,3 +78,24 @@ extension AddFolderWindowController: NSTextFieldDelegate { addFolderButton.isEnabled = !folderName.isEmpty } } + +// MARK: - Private + +private extension AddFolderWindowController { + + private func addFolderIfNeeded() { + guard let menuItem = accountPopupButton.selectedItem else { + return + } + + let account = menuItem.representedObject as! Account + AppDefaults.addFolderAccountID = account.accountID + + let folderName = self.folderNameTextField.stringValue + if folderName.isEmpty { + return + } + + account.ensureFolder(with: folderName) + } +} From bedf8a19e918f4f300b6efa3d48cfed4adafb814 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 24 Oct 2019 12:30:10 -0500 Subject: [PATCH 011/237] Pre-populate Feed Name on Feed Info. Issue #1163 --- iOS/Inspector/FeedInspectorView.swift | 28 +++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/iOS/Inspector/FeedInspectorView.swift b/iOS/Inspector/FeedInspectorView.swift index 19edf5b23..363103263 100644 --- a/iOS/Inspector/FeedInspectorView.swift +++ b/iOS/Inspector/FeedInspectorView.swift @@ -66,8 +66,12 @@ struct FeedInspectorView : View { Text(verbatim: self.viewModel.feedLinkURL) } } + .onDisappear { self.viewModel.save() } .navigationBarTitle(Text(verbatim: self.viewModel.nameForDisplay), displayMode: .inline) - .navigationBarItems(leading: Button(action: { self.viewController?.dismiss(animated: true) }) { Text("Done") } ) + .navigationBarItems(leading: Button(action: { + self.viewModel.save() + self.viewController?.dismiss(animated: true) + }) { Text("Done") } ) } } @@ -77,9 +81,11 @@ struct FeedInspectorView : View { let objectWillChange = ObservableObjectPublisher() let feed: Feed + @Published var name: String init(feed: Feed) { self.feed = feed + self.name = feed.nameForDisplay NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil) } @@ -97,20 +103,6 @@ struct FeedInspectorView : View { return feed.nameForDisplay } - var name: String { - get { - return feed.editedName ?? "" - } - set { - objectWillChange.send() - if newValue.isEmpty { - feed.editedName = nil - } else { - feed.editedName = newValue - } - } - } - var isNotifyAboutNewArticles: Bool { get { return feed.isNotifyAboutNewArticles ?? false @@ -143,6 +135,12 @@ struct FeedInspectorView : View { objectWillChange.send() } + func save() { + if name != nameForDisplay { + feed.editedName = name.isEmpty ? nil : name + } + } + } } From 3f81cd9b96a36401375e94de311cd7dcd39acff5 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 24 Oct 2019 12:46:53 -0500 Subject: [PATCH 012/237] Make sure that the search bar doesn't appear on device rotation. Issue #1171 --- .../MasterTimelineViewController.swift | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 2dd9a9a2a..edad2461b 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -48,20 +48,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange), name: .DisplayNameDidChange, object: nil) - - // Setup the Search Controller - searchController.delegate = self - searchController.searchResultsUpdater = self - searchController.obscuresBackgroundDuringPresentation = false - searchController.searchBar.delegate = self - searchController.searchBar.placeholder = NSLocalizedString("Search Articles", comment: "Search Articles") - searchController.searchBar.scopeButtonTitles = [ - NSLocalizedString("Here", comment: "Here"), - NSLocalizedString("All Articles", comment: "All Articles") - ] - navigationItem.searchController = searchController - definesPresentationContext = true - // Setup the Refresh Control refreshControl = UIRefreshControl() refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged) @@ -85,6 +71,21 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner updateProgressIndicatorIfNeeded() } + override func viewWillLayoutSubviews() { + // If you setup the Search Controller in viewWillLayoutSubviews it won't show by default on creation + searchController.delegate = self + searchController.searchResultsUpdater = self + searchController.obscuresBackgroundDuringPresentation = false + searchController.searchBar.delegate = self + searchController.searchBar.placeholder = NSLocalizedString("Search Articles", comment: "Search Articles") + searchController.searchBar.scopeButtonTitles = [ + NSLocalizedString("Here", comment: "Here"), + NSLocalizedString("All Articles", comment: "All Articles") + ] + navigationItem.searchController = searchController + definesPresentationContext = true + } + // MARK: Actions @IBAction func markAllAsRead(_ sender: Any) { From f8376b807a08b3d729d70c698172136c9dc34090 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Thu, 24 Oct 2019 21:12:58 -0700 Subject: [PATCH 013/237] Restore two-step process for OPML exporting. --- Mac/AppDelegate.swift | 4 +- .../OPML/ExportOPMLAccessoryView.xib | 83 -------------- .../ExportOPMLAccessoryViewController.swift | 62 ---------- .../OPML/ExportOPMLController.swift | 77 ------------- Mac/MainWindow/OPML/ExportOPMLSheet.xib | 104 +++++++++++++++++ .../OPML/ExportOPMLWindowController.swift | 106 ++++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 32 +++--- 7 files changed, 226 insertions(+), 242 deletions(-) delete mode 100644 Mac/MainWindow/OPML/ExportOPMLAccessoryView.xib delete mode 100644 Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift delete mode 100644 Mac/MainWindow/OPML/ExportOPMLController.swift create mode 100644 Mac/MainWindow/OPML/ExportOPMLSheet.xib create mode 100644 Mac/MainWindow/OPML/ExportOPMLWindowController.swift diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index f971da1ed..73519e152 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -57,7 +57,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, private var addFeedController: AddFeedController? private var addFolderWindowController: AddFolderWindowController? private var importOPMLController: ImportOPMLWindowController? - private var exportOPMLController: ExportOPMLController? + private var exportOPMLController: ExportOPMLWindowController? private var keyboardShortcutsWindowController: WebViewWindowController? private var inspectorWindowController: InspectorWindowController? private var crashReportWindowController: CrashReportWindowController? // For testing only @@ -438,7 +438,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, return } - exportOPMLController = ExportOPMLController() + exportOPMLController = ExportOPMLWindowController() exportOPMLController?.runSheetOnWindow(mainWindowController!.window!) } diff --git a/Mac/MainWindow/OPML/ExportOPMLAccessoryView.xib b/Mac/MainWindow/OPML/ExportOPMLAccessoryView.xib deleted file mode 100644 index 49376a471..000000000 --- a/Mac/MainWindow/OPML/ExportOPMLAccessoryView.xib +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Choose the account with the subscriptions you’d like to export. Subscriptions are exported in the standard OPML format, which most RSS readers can import. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift deleted file mode 100644 index d558d7da0..000000000 --- a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// ExportOPMLAccessoryViewController.swift -// NetNewsWire -// -// Created by Nate Weaver on 2019-10-20. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import AppKit -import Account - -protocol ExportOPMLAccessoryViewControllerDelegate: class { - func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) -} - -class ExportOPMLAccessoryViewController: NSViewController { - - @IBOutlet weak var accountPopUpButton: NSPopUpButton! - weak var delegate: ExportOPMLAccessoryViewControllerDelegate? - - var selectedAccount: Account? { - accountPopUpButton.selectedItem?.representedObject as? Account - } - - init(delegate: ExportOPMLAccessoryViewControllerDelegate) { - super.init(nibName: "ExportOPMLAccessoryView", bundle: nil) - self.delegate = delegate - } - - init() { - preconditionFailure("init() without delegate not implemented by design.") - } - - required init?(coder: NSCoder) { - preconditionFailure("ExportOPMLAccessoryView.init(coder) not implemented by design.") - } - - override func viewDidLoad() { - accountPopUpButton.removeAllItems() - - let menu = NSMenu() - accountPopUpButton.menu = menu - - for oneAccount in AccountManager.shared.sortedAccounts { - - let oneMenuItem = NSMenuItem() - oneMenuItem.title = oneAccount.nameForDisplay - oneMenuItem.representedObject = oneAccount - menu.addItem(oneMenuItem) - - if oneAccount.accountID == AppDefaults.exportOPMLAccountID { - accountPopUpButton.select(oneMenuItem) - } - - } - } - - @IBAction func accountSelected(_ popUpButton: NSPopUpButton) { - delegate!.selectedAccountDidChange(self) - } -} - diff --git a/Mac/MainWindow/OPML/ExportOPMLController.swift b/Mac/MainWindow/OPML/ExportOPMLController.swift deleted file mode 100644 index eb3fae3b0..000000000 --- a/Mac/MainWindow/OPML/ExportOPMLController.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// ExportOPMLController.swift -// NetNewsWire -// -// Created by Maurice Parker on 5/1/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import AppKit -import Account - -class ExportOPMLController { - - weak var savePanel: NSSavePanel? - - func runSheetOnWindow(_ hostWindow: NSWindow) { - - let accessoryViewController = ExportOPMLAccessoryViewController(delegate: self) - let panel = NSSavePanel() - panel.allowedFileTypes = ["opml"] - panel.allowsOtherFileTypes = false - panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML") - panel.title = NSLocalizedString("Export OPML", comment: "Export OPML") - panel.nameFieldLabel = NSLocalizedString("Export to:", comment: "Export OPML") - panel.message = NSLocalizedString("Choose a location for the exported OPML file.", comment: "Export OPML") - panel.isExtensionHidden = false - panel.accessoryView = accessoryViewController.view - - updateNameFieldStringValueIfAppropriate(savePanel: panel, from: accessoryViewController, force: true) - - savePanel = panel - - panel.beginSheetModal(for: hostWindow) { result in - if result == NSApplication.ModalResponse.OK, let url = panel.url { - DispatchQueue.main.async { - guard let account = accessoryViewController.selectedAccount else { - return - } - let filename = url.lastPathComponent - let opmlString = OPMLExporter.OPMLString(with: account, title: filename) - do { - try opmlString.write(to: url, atomically: true, encoding: .utf8) - } - catch let error as NSError { - NSApplication.shared.presentError(error) - } - } - } - } - } -} - -extension ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { - - func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) { - if let savePanel = savePanel { - self.updateNameFieldStringValueIfAppropriate(savePanel: savePanel, from: accessoryViewController) - } - } -} - -private extension ExportOPMLController { - - func updateNameFieldStringValueIfAppropriate(savePanel panel: NSSavePanel, from accessoryViewController: ExportOPMLAccessoryViewController, force: Bool = false) { - - if !force && !panel.nameFieldStringValue.hasPrefix("Subscriptions-") { - return - } - - guard let account = accessoryViewController.selectedAccount else { - return - } - let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) - panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" - - } -} diff --git a/Mac/MainWindow/OPML/ExportOPMLSheet.xib b/Mac/MainWindow/OPML/ExportOPMLSheet.xib new file mode 100644 index 000000000..e6bc9f85b --- /dev/null +++ b/Mac/MainWindow/OPML/ExportOPMLSheet.xib @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Choose the account with the subscriptions you’d like to export. Subscriptions are exported in the standard OPML format, which most RSS readers can import. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift new file mode 100644 index 000000000..cdbda991c --- /dev/null +++ b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift @@ -0,0 +1,106 @@ +// +// ExportOPMLWindowController.swift +// NetNewsWire +// +// Created by Maurice Parker on 5/1/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import AppKit +import Account + +class ExportOPMLWindowController: NSWindowController { + + @IBOutlet weak var accountPopUpButton: NSPopUpButton! + private weak var hostWindow: NSWindow? + + convenience init() { + self.init(windowNibName: NSNib.Name("ExportOPMLSheet")) + } + + override func windowDidLoad() { + accountPopUpButton.removeAllItems() + + let menu = NSMenu() + accountPopUpButton.menu = menu + + for oneAccount in AccountManager.shared.sortedAccounts { + + let oneMenuItem = NSMenuItem() + oneMenuItem.title = oneAccount.nameForDisplay + oneMenuItem.representedObject = oneAccount + menu.addItem(oneMenuItem) + + if oneAccount.accountID == AppDefaults.exportOPMLAccountID { + accountPopUpButton.select(oneMenuItem) + } + + } + } + + // MARK: API + + func runSheetOnWindow(_ hostWindow: NSWindow) { + + self.hostWindow = hostWindow + + if AccountManager.shared.accounts.count == 1 { + let account = AccountManager.shared.accounts.first! + exportOPML(account: account) + } else { + hostWindow.beginSheet(window!) + } + + } + + // MARK: Actions + + @IBAction func cancel(_ sender: Any) { + hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) + } + + @IBAction func exportOPML(_ sender: Any) { + + guard let menuItem = accountPopUpButton.selectedItem else { + return + } + + let account = menuItem.representedObject as! Account + AppDefaults.exportOPMLAccountID = account.accountID + hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) + exportOPML(account: account) + + } + + func exportOPML(account: Account) { + + let panel = NSSavePanel() + panel.allowedFileTypes = ["opml"] + panel.allowsOtherFileTypes = false + panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML") + panel.title = NSLocalizedString("Export OPML", comment: "Export OPML") + panel.nameFieldLabel = NSLocalizedString("Export to:", comment: "Export OPML") + panel.message = NSLocalizedString("Choose a location for the exported OPML file.", comment: "Export OPML") + panel.isExtensionHidden = false + + let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) + panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" + + panel.beginSheetModal(for: hostWindow!) { result in + if result == NSApplication.ModalResponse.OK, let url = panel.url { + DispatchQueue.main.async { + let filename = url.lastPathComponent + let opmlString = OPMLExporter.OPMLString(with: account, title: filename) + do { + try opmlString.write(to: url, atomically: true, encoding: String.Encoding.utf8) + } + catch let error as NSError { + NSApplication.shared.presentError(error) + } + } + } + } + + } + +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 1a9fa9f4e..571fbf842 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */; }; 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */; }; 5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */; }; - 5144EA43227A380F00D19003 /* ExportOPMLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA42227A380F00D19003 /* ExportOPMLController.swift */; }; 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */; }; 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; 51543685228F6753005E1CDF /* DetailAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51543684228F6753005E1CDF /* DetailAccountViewController.swift */; }; @@ -231,6 +230,8 @@ 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A979E1ED9F130007D329B /* SidebarCell.swift */; }; 849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849C64671ED37A5D003D8FC0 /* Assets.xcassets */; }; + 849C78902362AAFC009A71E4 /* ExportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 849C78872362AAFB009A71E4 /* ExportOPMLSheet.xib */; }; + 849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */; }; 849EE70F203919360082A1EA /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE70E203919360082A1EA /* AppAssets.swift */; }; 849EE72120391F560082A1EA /* SharingServicePickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */; }; 84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; @@ -312,8 +313,6 @@ 84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; }; 84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; - B24C4F7B235D39D40000B924 /* ExportOPMLAccessoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */; }; - B24C4F85235D39F30000B924 /* ExportOPMLAccessoryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */; }; D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; }; D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; }; D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */; }; @@ -687,7 +686,6 @@ 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddLocalWindowController.swift; sourceTree = ""; }; 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ImportOPMLSheet.xib; sourceTree = ""; }; 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportOPMLWindowController.swift; sourceTree = ""; }; - 5144EA42227A380F00D19003 /* ExportOPMLController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLController.swift; sourceTree = ""; }; 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedbinWindowController.swift; sourceTree = ""; }; 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = ""; }; 51543684228F6753005E1CDF /* DetailAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailAccountViewController.swift; sourceTree = ""; }; @@ -850,6 +848,8 @@ 849C64601ED37A5D003D8FC0 /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; 849C64671ED37A5D003D8FC0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 849C64711ED37A5D003D8FC0 /* NetNewsWireTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NetNewsWireTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 849C78872362AAFB009A71E4 /* ExportOPMLSheet.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ExportOPMLSheet.xib; sourceTree = ""; }; + 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportOPMLWindowController.swift; sourceTree = ""; }; 849EE70E203919360082A1EA /* AppAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = ""; }; 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServicePickerDelegate.swift; sourceTree = ""; }; 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToMicroBlogCommand.swift; sourceTree = ""; }; @@ -925,8 +925,6 @@ 84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; }; 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; }; - B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLAccessoryViewController.swift; sourceTree = ""; }; - B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExportOPMLAccessoryView.xib; sourceTree = ""; }; B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; }; B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; }; D553737C20186C1F006D8857 /* Article+Scriptability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Article+Scriptability.swift"; sourceTree = ""; }; @@ -1037,9 +1035,8 @@ children = ( 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */, 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */, - 5144EA42227A380F00D19003 /* ExportOPMLController.swift */, - B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */, - B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */, + 849C78872362AAFB009A71E4 /* ExportOPMLSheet.xib */, + 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */, ); path = OPML; sourceTree = ""; @@ -1992,12 +1989,12 @@ ORGANIZATIONNAME = "Ranchero Software"; TargetAttributes = { 6581C73220CED60000F4AD34 = { - DevelopmentTeam = M72QZ9W58G; - ProvisioningStyle = Automatic; + DevelopmentTeam = M8L2WTLA8W; + ProvisioningStyle = Manual; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = M8L2WTLA8W; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -2013,8 +2010,8 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = M72QZ9W58G; - ProvisioningStyle = Automatic; + DevelopmentTeam = M8L2WTLA8W; + ProvisioningStyle = Manual; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; @@ -2023,7 +2020,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = 9C84TZ7Q6Z; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -2289,8 +2286,8 @@ 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */, 844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */, 84A3EE5F223B667F00557320 /* DefaultFeeds.opml in Resources */, - B24C4F85235D39F30000B924 /* ExportOPMLAccessoryView.xib in Resources */, 8459D0F92355794C0050076F /* NNW3OpenPanelAccessoryView.xib in Resources */, + 849C78902362AAFC009A71E4 /* ExportOPMLSheet.xib in Resources */, 84C9FC8222629E4800D921D6 /* Preferences.storyboard in Resources */, 849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */, 848362FD2262A30800DA1D35 /* styleSheet.css in Resources */, @@ -2479,6 +2476,7 @@ 84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */, 845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */, 84AD1EBA2031649C00BC20B7 /* SmartFeedPasteboardWriter.swift in Sources */, + 849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */, 84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */, 849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */, 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */, @@ -2500,7 +2498,6 @@ 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */, - B24C4F7B235D39D40000B924 /* ExportOPMLAccessoryViewController.swift in Sources */, 84C9FC7B22629E1200D921D6 /* AccountsControlsBackgroundView.swift in Sources */, 84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */, 84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */, @@ -2511,7 +2508,6 @@ 849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */, 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */, 84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */, - 5144EA43227A380F00D19003 /* ExportOPMLController.swift in Sources */, 842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */, 84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */, D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */, From eec008ca5b9a281cf9d0d5a3d4a4715d94482413 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Thu, 24 Oct 2019 21:41:20 -0700 Subject: [PATCH 014/237] =?UTF-8?q?Remove=20Info.plist=20from=20NetNewsWir?= =?UTF-8?q?e=20Tests=20copy=20files=20phase,=20since=20it=20shouldn?= =?UTF-8?q?=E2=80=99t=20be=20there.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NetNewsWire.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 571fbf842..28526c805 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -309,7 +309,6 @@ 84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; }; 84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; }; 84F9EAF6213660A100CF2DE4 /* NetNewsWireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE3213660A100CF2DE4 /* NetNewsWireTests.swift */; }; - 84F9EAF7213660A100CF2DE4 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 84F9EAE4213660A100CF2DE4 /* Info.plist */; }; 84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; }; 84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; @@ -2307,7 +2306,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 84F9EAF7213660A100CF2DE4 /* Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; From cd4135bf9a9dfe6b418eeab26a9ab0a3a79a370f Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Thu, 24 Oct 2019 21:48:24 -0700 Subject: [PATCH 015/237] Remove old, comment-out update method in ArticlesTable. --- .../ArticlesDatabase/ArticlesTable.swift | 59 ------------------- 1 file changed, 59 deletions(-) diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index d9bf36e7c..418e53a1a 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -272,65 +272,6 @@ final class ArticlesTable: DatabaseTable { } } -// func update(_ feedID: String, _ parsedItems: Set, _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) { -// if parsedItems.isEmpty { -// completion(nil, nil) -// return -// } -// -// // 1. Ensure statuses for all the incoming articles. -// // 2. Create incoming articles with parsedItems. -// // 3. Ignore incoming articles that are userDeleted || (!starred and really old) -// // 4. Fetch all articles for the feed. -// // 5. Create array of Articles not in database and save them. -// // 6. Create array of updated Articles and save what’s changed. -// // 7. Call back with new and updated Articles. -// // 8. Update search index. -// -// let articleIDs = Set(parsedItems.map { $0.articleID }) -// -// self.queue.update { (database) in -// let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1 -// assert(statusesDictionary.count == articleIDs.count) -// -// let allIncomingArticles = Article.articlesWithParsedItems(parsedItems, self.accountID, feedID, statusesDictionary) //2 -// if allIncomingArticles.isEmpty { -// self.callUpdateArticlesCompletionBlock(nil, nil, completion) -// return -// } -// -// let incomingArticles = self.filterIncomingArticles(allIncomingArticles) //3 -// if incomingArticles.isEmpty { -// self.callUpdateArticlesCompletionBlock(nil, nil, completion) -// return -// } -// -// let fetchedArticles = self.fetchArticlesForFeedID(feedID, withLimits: false, database) //4 -// let fetchedArticlesDictionary = fetchedArticles.dictionary() -// -// let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5 -// let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6 -// -// self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7 -// -// // 8. Update search index. -// var articlesToIndex = Set
() -// if let newArticles = newArticles { -// articlesToIndex.formUnion(newArticles) -// } -// if let updatedArticles = updatedArticles { -// articlesToIndex.formUnion(updatedArticles) -// } -// let articleIDs = articlesToIndex.articleIDs() -// if articleIDs.isEmpty { -// return -// } -// DispatchQueue.main.async { -// self.searchTable.ensureIndexedArticles(for: articleIDs) -// } -// } -// } - func ensureStatuses(_ articleIDs: Set, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool) { self.queue.update { (database) in let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, defaultRead, database) From 3354d5a56972803a3f55fb4840124b7f7114d6b6 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Thu, 24 Oct 2019 22:28:26 -0700 Subject: [PATCH 016/237] Delete articles and statuses from feeds no longer subscribed-to. At startup. Fix #899. --- Frameworks/Account/Account.swift | 1 + .../ArticlesDatabase/ArticlesDatabase.swift | 9 ++++++ .../ArticlesDatabase/ArticlesTable.swift | 29 +++++++++++++++++++ .../ArticlesDatabase/StatusesTable.swift | 6 ++++ 4 files changed, 45 insertions(+) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index b4b1ff707..4c9258c83 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -254,6 +254,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, pullObjectsFromDisk() DispatchQueue.main.async { + self.database.cleanupDatabaseAtStartup(subscribedToFeedIDs: self.flattenedFeeds().feedIDs()) self.fetchAllUnreadCounts() } diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index 5aa003d30..e14dac4de 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -152,6 +152,15 @@ public final class ArticlesDatabase { public func emptyCaches() { articlesTable.emptyCaches() } + + // MARK: - Cleanup + + // These are to be used only at startup. These are to prevent the database from growing forever. + + /// Calls the various clean-up functions. + public func cleanupDatabaseAtStartup(subscribedToFeedIDs: Set) { + articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToFeedIDs) + } } // MARK: - Private diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index 418e53a1a..a0b6edbd2 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -423,6 +423,31 @@ final class ArticlesTable: DatabaseTable { self.databaseArticlesCache = [String: DatabaseArticle]() } } + + // MARK: - Cleanup + + /// Delete articles from feeds that are no longer in the current set of subscribed-to feeds. + /// This deletes from the articles and articleStatuses tables, + /// and, via a trigger, it also deletes from the search index. + func deleteArticlesNotInSubscribedToFeedIDs(_ feedIDs: Set) { + if feedIDs.isEmpty { + return + } + queue.run { (database) in + let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))! + let sql = "select articleID from articles where feedID not in \(placeholders);" + let parameters = Array(feedIDs) as [Any] + guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else { + return + } + let articleIDs = resultSet.mapToSet{ $0.string(forColumn: DatabaseKey.articleID) } + if articleIDs.isEmpty { + return + } + self.removeArticles(articleIDs, database) + self.statusesTable.removeStatuses(articleIDs, database) + } + } } // MARK: - Private @@ -730,6 +755,10 @@ private extension ArticlesTable { // Drop Articles that we can ignore. return Set(articles.filter{ !statusIndicatesArticleIsIgnorable($0.status) }) } + + func removeArticles(_ articleIDs: Set, _ database: FMDatabase) { + deleteRowsWhere(key: DatabaseKey.articleID, equalsAnyValue: Array(articleIDs), in: database) + } } private extension Set where Element == ParsedItem { diff --git a/Frameworks/ArticlesDatabase/StatusesTable.swift b/Frameworks/ArticlesDatabase/StatusesTable.swift index fa5a741ab..65615c1b0 100644 --- a/Frameworks/ArticlesDatabase/StatusesTable.swift +++ b/Frameworks/ArticlesDatabase/StatusesTable.swift @@ -134,6 +134,12 @@ final class StatusesTable: DatabaseTable { return d } + + // MARK: - Cleanup + + func removeStatuses(_ articleIDs: Set, _ database: FMDatabase) { + deleteRowsWhere(key: DatabaseKey.articleID, equalsAnyValue: Array(articleIDs), in: database) + } } // MARK: - Private From fa24e8a863eb62d8f9a1ea984dce266e389f510b Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 25 Oct 2019 12:56:28 -0500 Subject: [PATCH 017/237] Make sure suite name is initialized before using it --- iOS/AppDefaults.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index 31d006641..09cd6a317 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -10,12 +10,9 @@ import UIKit struct AppDefaults { - private static var suiteName: String = { - let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String - return "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)" - }() - static var shared: UserDefaults { + let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String + let suiteName = "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)" return UserDefaults.init(suiteName: suiteName)! } From 6a281c7672c42833ecdf9822f5326d431a899533 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 25 Oct 2019 13:34:59 -0500 Subject: [PATCH 018/237] Replace Mark All as Read with progress indicator. Issues #1157 and #1165 --- NetNewsWire.xcodeproj/project.pbxproj | 26 ++-- iOS/Article/ArticleViewController.swift | 16 -- iOS/Base.lproj/LaunchScreenPad.storyboard | 7 +- iOS/Base.lproj/LaunchScreenPhone.storyboard | 7 +- iOS/Base.lproj/Main.storyboard | 21 +-- iOS/MasterFeed/MasterFeedViewController.swift | 40 +++-- iOS/MasterFeed/RefreshProgressView.swift | 70 +++++++++ iOS/MasterFeed/RefreshProgressView.xib | 49 ++++++ .../MasterTimelineViewController.swift | 29 ---- .../UndoAvailableAlertController.swift | 0 iOS/Progress/NavigationProgressView.swift | 132 ---------------- .../UINavigationController+Progress.swift | 145 ------------------ iOS/SceneCoordinator.swift | 9 -- 13 files changed, 153 insertions(+), 398 deletions(-) create mode 100644 iOS/MasterFeed/RefreshProgressView.swift create mode 100644 iOS/MasterFeed/RefreshProgressView.xib rename iOS/{MasterFeed => MasterTimeline}/UndoAvailableAlertController.swift (100%) delete mode 100644 iOS/Progress/NavigationProgressView.swift delete mode 100644 iOS/Progress/UINavigationController+Progress.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 4102a510f..b1a501672 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -93,8 +93,6 @@ 517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */; }; 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; }; 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; }; - 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */; }; - 5183CCDF226F1FCC0010922C /* UINavigationController+Progress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCDE226F1FCC0010922C /* UINavigationController+Progress.swift */; }; 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 5183CCE6226F4E110010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 5183CCE8226F68D90010922C /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; @@ -194,6 +192,8 @@ 51C452B42265141B00C03939 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51C452B32265141B00C03939 /* WebKit.framework */; }; 51C452B82265178500C03939 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 51C452B72265178500C03939 /* styleSheet.css */; }; 51CC9B3E231720B2000E842F /* MasterFeedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CC9B3D231720B2000E842F /* MasterFeedDataSource.swift */; }; + 51CE1C0923621EDA005548FC /* RefreshProgressView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51CE1C0823621EDA005548FC /* RefreshProgressView.xib */; }; + 51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CE1C0A23622006005548FC /* RefreshProgressView.swift */; }; 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */; }; 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; @@ -1251,8 +1251,6 @@ 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleViewControllerWebViewProvider.swift; sourceTree = ""; }; 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = ""; }; 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = ""; }; - 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProgressView.swift; sourceTree = ""; }; - 5183CCDE226F1FCC0010922C /* UINavigationController+Progress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Progress.swift"; sourceTree = ""; }; 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshInterval.swift; sourceTree = ""; }; 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountRefreshTimer.swift; sourceTree = ""; }; 518651AB23555EB20078E021 /* NNW3Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NNW3Document.swift; sourceTree = ""; }; @@ -1300,6 +1298,8 @@ 51C452B32265141B00C03939 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; 51C452B72265178500C03939 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; 51CC9B3D231720B2000E842F /* MasterFeedDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedDataSource.swift; sourceTree = ""; }; + 51CE1C0823621EDA005548FC /* RefreshProgressView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RefreshProgressView.xib; sourceTree = ""; }; + 51CE1C0A23622006005548FC /* RefreshProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshProgressView.swift; sourceTree = ""; }; 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineDataSource.swift; sourceTree = ""; }; 51D87EE02311D34700E63F03 /* ActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityType.swift; sourceTree = ""; }; 51E3EB32229AB02C00645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; @@ -1766,15 +1766,6 @@ path = Account; sourceTree = ""; }; - 5183CCDB226F1EEB0010922C /* Progress */ = { - isa = PBXGroup; - children = ( - 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */, - 5183CCDE226F1FCC0010922C /* UINavigationController+Progress.swift */, - ); - path = Progress; - sourceTree = ""; - }; 5183CCEA226F70350010922C /* Timer */ = { isa = PBXGroup; children = ( @@ -1854,9 +1845,10 @@ 51C4525D226508F600C03939 /* MasterFeed */ = { isa = PBXGroup; children = ( - FFD43E372340F320009E5CA3 /* UndoAvailableAlertController.swift */, 51C45264226508F600C03939 /* MasterFeedViewController.swift */, 51CC9B3D231720B2000E842F /* MasterFeedDataSource.swift */, + 51CE1C0A23622006005548FC /* RefreshProgressView.swift */, + 51CE1C0823621EDA005548FC /* RefreshProgressView.xib */, 51C45260226508F600C03939 /* Cell */, ); path = MasterFeed; @@ -1881,6 +1873,7 @@ 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */, 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */, 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */, + FFD43E372340F320009E5CA3 /* UndoAvailableAlertController.swift */, 51C4526F2265091600C03939 /* Cell */, ); path = MasterTimeline; @@ -2538,7 +2531,6 @@ 51C452802265093600C03939 /* Add */, 5123DB95233EC69300282CC9 /* Inspector */, 513145F9235A55A700387FDC /* Intents */, - 5183CCDB226F1EEB0010922C /* Progress */, 5183CCEB227117C70010922C /* Settings */, 519D740423243C68008BB345 /* Model Extensions */, 51C45245226506C800C03939 /* UIKit Extensions */, @@ -3403,6 +3395,7 @@ 51A1699A235E10D700EB091F /* Settings.storyboard in Resources */, 49F40DF92335B71000552BF4 /* newsfoot.js in Resources */, 51F85BEF2272520B00C787DC /* Thanks.rtf in Resources */, + 51CE1C0923621EDA005548FC /* RefreshProgressView.xib in Resources */, 84C9FC9D2262A1A900D921D6 /* Assets.xcassets in Resources */, 514219582353C28900E07E2C /* main_ios.js in Resources */, 51C452B82265178500C03939 /* styleSheet.css in Resources */, @@ -3889,7 +3882,6 @@ 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */, 51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */, 513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */, - 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */, 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */, 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, @@ -3915,7 +3907,6 @@ 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */, 51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, - 5183CCDF226F1FCC0010922C /* UINavigationController+Progress.swift in Sources */, 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */, 5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */, 512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */, @@ -3982,6 +3973,7 @@ 51FFF0C4235EE8E5002762AA /* VibrantButton.swift in Sources */, 513228FC233037630033D4ED /* Reachability.swift in Sources */, 51C45259226508D300C03939 /* AppDefaults.swift in Sources */, + 51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */, 511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */, 51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */, 51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */, diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index c73636727..a479e693f 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -96,7 +96,6 @@ class ArticleViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) @@ -126,11 +125,6 @@ class ArticleViewController: UIViewController { } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - updateProgressIndicatorIfNeeded() - } - func updateUI() { guard let article = currentArticle else { @@ -209,10 +203,6 @@ class ArticleViewController: UIViewController { } } - @objc func progressDidChange(_ note: Notification) { - updateProgressIndicatorIfNeeded() - } - @objc func contentSizeCategoryDidChange(_ note: Notification) { reloadHTML() } @@ -427,12 +417,6 @@ private struct ImageClickMessage: Codable { private extension ArticleViewController { - func updateProgressIndicatorIfNeeded() { - if !(UIDevice.current.userInterfaceIdiom == .pad) { - navigationController?.updateAccountRefreshProgressIndicator() - } - } - func imageWasClicked(body: String?) { guard let body = body, let data = body.data(using: .utf8), diff --git a/iOS/Base.lproj/LaunchScreenPad.storyboard b/iOS/Base.lproj/LaunchScreenPad.storyboard index 81d675131..070aca5aa 100644 --- a/iOS/Base.lproj/LaunchScreenPad.storyboard +++ b/iOS/Base.lproj/LaunchScreenPad.storyboard @@ -1,8 +1,8 @@ - + - + @@ -65,7 +65,6 @@ - @@ -114,6 +113,6 @@ - + diff --git a/iOS/Base.lproj/LaunchScreenPhone.storyboard b/iOS/Base.lproj/LaunchScreenPhone.storyboard index 7e19f8713..a61b6f5d9 100644 --- a/iOS/Base.lproj/LaunchScreenPhone.storyboard +++ b/iOS/Base.lproj/LaunchScreenPhone.storyboard @@ -1,8 +1,8 @@ - + - + @@ -65,7 +65,6 @@ - @@ -114,6 +113,6 @@ - + diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard index 994202c66..e767ead95 100644 --- a/iOS/Base.lproj/Main.storyboard +++ b/iOS/Base.lproj/Main.storyboard @@ -190,22 +190,7 @@ - - - - - - - - - - - - - - - - + @@ -218,10 +203,6 @@ - - - - diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 15a86ba80..c0c606a96 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -14,8 +14,8 @@ import RSTree class MasterFeedViewController: UITableViewController, UndoableCommandRunner { - @IBOutlet private weak var markAllAsReadButton: UIBarButtonItem! - @IBOutlet private weak var addNewItemButton: UIBarButtonItem! + private var refreshProgressView: RefreshProgressView! + private var addNewItemButton: UIBarButtonItem! private lazy var dataSource = makeDataSource() var undoableCommands = [UndoableCommand]() @@ -54,12 +54,13 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(feedMetadataDidChange(_:)), name: .FeedMetadataDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) refreshControl = UIRefreshControl() refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged) + configureToolbar() + updateUI() becomeFirstResponder() @@ -73,7 +74,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - navigationController?.updateAccountRefreshProgressIndicator() } // MARK: Notifications @@ -133,10 +133,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { discloseFeed(feed) } - @objc func progressDidChange(_ note: Notification) { - navigationController?.updateAccountRefreshProgressIndicator() - } - @objc func contentSizeCategoryDidChange(_ note: Notification) { applyChanges(animate: false) } @@ -344,19 +340,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { @IBAction func settings(_ sender: UIBarButtonItem) { coordinator.showSettings() } - - @IBAction func markAllAsRead(_ sender: Any) { - if coordinator.displayUndoAvailableTip { - let alertController = UndoAvailableAlertController.alert { [weak self] _ in - self?.coordinator.displayUndoAvailableTip = false - self?.coordinator.markAllAsRead() - } - - present(alertController, animated: true) - } else { - coordinator.markAllAsRead() - } - } @IBAction func add(_ sender: UIBarButtonItem) { coordinator.showAdd(.feed) @@ -573,8 +556,21 @@ extension MasterFeedViewController: MasterFeedTableViewCellDelegate { private extension MasterFeedViewController { + func configureToolbar() { + guard let refreshProgressView = Bundle.main.loadNibNamed("RefreshProgressView", owner: self, options: nil)?[0] as? RefreshProgressView else { + return + } + + self.refreshProgressView = refreshProgressView + + let refreshProgressItemButton = UIBarButtonItem(customView: refreshProgressView) + let spaceItemButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + addNewItemButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add(_:))) + setToolbarItems([refreshProgressItemButton, spaceItemButton, addNewItemButton], animated: false) + } + func updateUI() { - markAllAsReadButton.isEnabled = coordinator.isAnyUnreadAvailable + refreshProgressView.updateRefreshLabel() addNewItemButton.isEnabled = !AccountManager.shared.activeAccounts.isEmpty } diff --git a/iOS/MasterFeed/RefreshProgressView.swift b/iOS/MasterFeed/RefreshProgressView.swift new file mode 100644 index 000000000..a153ada73 --- /dev/null +++ b/iOS/MasterFeed/RefreshProgressView.swift @@ -0,0 +1,70 @@ +// +// RefeshProgressView.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 10/24/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit +import Account + +class RefreshProgressView: UIView { + + @IBOutlet weak var progressView: UIProgressView! + @IBOutlet weak var label: UILabel! + private lazy var progressWidth = progressView.widthAnchor.constraint(equalToConstant: 100.0) + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + func commonInit() { + NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) + } + + func updateRefreshLabel() { + if let refreshDate = AppDefaults.lastRefresh { + let relativeDateTimeFormatter = RelativeDateTimeFormatter() + relativeDateTimeFormatter.dateTimeStyle = .named + let refreshed = relativeDateTimeFormatter.localizedString(for: refreshDate, relativeTo: Date()) + let localizedRefreshText = NSLocalizedString("Refreshed %@", comment: "Refreshed") + let refreshText = NSString.localizedStringWithFormat(localizedRefreshText as NSString, refreshed) as String + label.text = refreshText + } + + } + @objc func progressDidChange(_ note: Notification) { + + let progress = AccountManager.shared.combinedRefreshProgress + + if progress.isComplete { + progressView.progress = 1 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.updateRefreshLabel() + self.label.isHidden = false + self.progressView.isHidden = true + self.progressWidth.isActive = false + } + } else { + label.isHidden = true + progressView.isHidden = false + self.progressWidth.isActive = true + let percent = Float(progress.numberCompleted) / Float(progress.numberOfTasks) + progressView.progress = percent + } + + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + +} + diff --git a/iOS/MasterFeed/RefreshProgressView.xib b/iOS/MasterFeed/RefreshProgressView.xib new file mode 100644 index 000000000..6fa3b68d0 --- /dev/null +++ b/iOS/MasterFeed/RefreshProgressView.xib @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index edad2461b..d27e14a20 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -44,14 +44,9 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange), name: .DisplayNameDidChange, object: nil) - // Setup the Refresh Control - refreshControl = UIRefreshControl() - refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged) - // Configure the table tableView.dataSource = dataSource numberOfTextLines = AppDefaults.timelineNumberOfLines @@ -66,11 +61,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner super.viewWillAppear(animated) } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - updateProgressIndicatorIfNeeded() - } - override func viewWillLayoutSubviews() { // If you setup the Search Controller in viewWillLayoutSubviews it won't show by default on creation searchController.delegate = self @@ -368,10 +358,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner reloadAllVisibleCells() } - @objc func progressDidChange(_ note: Notification) { - updateProgressIndicatorIfNeeded() - } - @objc func displayNameDidChange(_ note: Notification) { titleView?.label.text = coordinator.timelineName } @@ -455,15 +441,6 @@ extension MasterTimelineViewController: UISearchBarDelegate { private extension MasterTimelineViewController { - @objc private func refreshAccounts(_ sender: Any) { - refreshControl?.endRefreshing() - // This is a hack to make sure that an error dialog doesn't interfere with dismissing the refreshControl. - // If the error dialog appears too closely to the call to endRefreshing, then the refreshControl never disappears. - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present(self)) - } - } - func resetUI() { title = coordinator.timelineName @@ -521,12 +498,6 @@ private extension MasterTimelineViewController { } } - func updateProgressIndicatorIfNeeded() { - if !coordinator.isThreePanelMode { - navigationController?.updateAccountRefreshProgressIndicator() - } - } - func applyChanges(animate: Bool, completion: (() -> Void)? = nil) { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([0]) diff --git a/iOS/MasterFeed/UndoAvailableAlertController.swift b/iOS/MasterTimeline/UndoAvailableAlertController.swift similarity index 100% rename from iOS/MasterFeed/UndoAvailableAlertController.swift rename to iOS/MasterTimeline/UndoAvailableAlertController.swift diff --git a/iOS/Progress/NavigationProgressView.swift b/iOS/Progress/NavigationProgressView.swift deleted file mode 100644 index e28562ba8..000000000 --- a/iOS/Progress/NavigationProgressView.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// NavigationProgressView.swift -// KYNavigationProgress -// -// Created by kyo__hei on 2015/12/29. -// Copyright (c) 2015 kyo__hei. All rights reserved. -// -// Original project: https://github.com/ykyouhei/KYNavigationProgress - -import UIKit - -public final class NavigationProgressView: UIView { - - /* ====================================================================== */ - // MARK: - Properties - /* ====================================================================== */ - - internal var progress: Float = 0 { - didSet { - progress = min(1, progress) - barWidthConstraint.constant = bounds.width * CGFloat(progress) - } - } - - internal let bar = UIView() - - @objc public dynamic var progressTintColor: UIColor? = AppAssets.primaryAccentColor { - didSet { - bar.backgroundColor = progressTintColor - } - } - - @objc public dynamic var trackTintColor: UIColor? = .clear { - didSet { - backgroundColor = trackTintColor - } - } - - fileprivate let barWidthConstraint: NSLayoutConstraint - - override public var frame: CGRect { - didSet { - let tmpProgress = progress - progress = tmpProgress - } - } - - /* ====================================================================== */ - // MARK: - initializer - /* ====================================================================== */ - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override init(frame: CGRect) { - barWidthConstraint = NSLayoutConstraint( - item: bar, - attribute: .width, - relatedBy: .equal, - toItem: nil, - attribute: .notAnAttribute, - multiplier: 1, - constant: frame.width * CGFloat(progress)) - - super.init(frame: frame) - - let leftConstraint = NSLayoutConstraint( - item: bar, - attribute: .left, - relatedBy: .equal, - toItem: self, - attribute: .left, - multiplier: 1, - constant: 0) - - let bottomConstraint = NSLayoutConstraint( - item: bar, - attribute: .bottom, - relatedBy: .equal, - toItem: self, - attribute: .bottom, - multiplier: 1, - constant: 0) - - let topConstraint = NSLayoutConstraint( - item: bar, - attribute: .top, - relatedBy: .equal, - toItem: self, - attribute: .top, - multiplier: 1, - constant: 0) - - addSubview(bar) - - backgroundColor = trackTintColor - - bar.backgroundColor = progressTintColor - bar.translatesAutoresizingMaskIntoConstraints = false - addConstraints([ - barWidthConstraint, - leftConstraint, - topConstraint, - bottomConstraint]) - } - - - /* ====================================================================== */ - // MARK: - Notification - /* ====================================================================== */ - - func deviceDidRotate(_ notification: Notification) { - } - - - /* ====================================================================== */ - // MARK: - Method - /* ====================================================================== */ - - internal func setProgress(_ progress: Float, animated: Bool, completion: @escaping () -> Void) { - let duration: TimeInterval = animated ? 0.2 : 0 - - self.progress = progress - - UIView.animate(withDuration: duration, animations: { self.layoutIfNeeded() }) { _ in - completion() - } - - } - -} diff --git a/iOS/Progress/UINavigationController+Progress.swift b/iOS/Progress/UINavigationController+Progress.swift deleted file mode 100644 index 6d80f9b92..000000000 --- a/iOS/Progress/UINavigationController+Progress.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// UINavigationController+Progress.swift -// KYNavigationProgress -// -// Created by kyo__hei on 2015/12/29. -// Copyright (c) 2015 kyo__hei. All rights reserved. -// -// Original project: https://github.com/ykyouhei/KYNavigationProgress - -import UIKit -import Account - -private let constraintIdentifier = "progressHeightConstraint" - -public extension UINavigationController { - - /* ====================================================================== */ - // MARK: - Properties - /* ====================================================================== */ - - /** - Default is 2.0 - */ - var progressHeight: CGFloat { - get { return progressView.frame.height } - set { - progressView.frame.origin.y = navigationBar.frame.height - newValue - progressView.frame.size.height = newValue - } - } - - /** - The color shown for the portion of the progress bar that is not filled. - default is clear color. - */ - var trackTintColor: UIColor? { - get { return progressView.trackTintColor } - set { progressView.trackTintColor = newValue } - } - - /** - The color shown for the portion of the progress bar that is filled. - default is (r: 0, g: 122, b: 225, a: 255. - */ - var progressTintColor: UIColor? { - get { return progressView.progressTintColor } - set { progressView.progressTintColor = newValue } - } - - /** - The current progress is represented by a floating-point value between 0.0 and 1.0, - inclusive, where 1.0 indicates the completion of the task. The default value is 0.0. - */ - var progress: Float { - get { return progressView.progress } - set { progressView.progress = newValue } - } - - - private var progressView: NavigationProgressView { - - for subview in navigationBar.subviews { - if let progressView = subview as? NavigationProgressView { - return progressView - } - } - - let defaultHeight = CGFloat(2) - let frame = CGRect( - x: 0, - y: navigationBar.frame.height - defaultHeight, - width: navigationBar.frame.width, - height: defaultHeight - ) - let progressView = NavigationProgressView(frame: frame) - - navigationBar.addSubview(progressView) - - progressView.autoresizingMask = [ - .flexibleWidth, .flexibleTopMargin - ] - - - return progressView - } - - - /* ====================================================================== */ - // MARK: - Public Method - /* ====================================================================== */ - - /** - Adjusts the current progress shown by the receiver, optionally animating the change. - - - parameter progress: The new progress value. - - parameter animated: true if the change should be animated, false if the change should happen immediately. - */ - func setProgress(_ progress: Float, animated: Bool, completion: @escaping () -> Void) { - progressView.bar.alpha = 1 - progressView.setProgress(progress, animated: animated, completion: completion) - } - - /** - While progress is changed to 1.0, the bar will fade out. After that, progress will be 0.0. - */ - func finishProgress() { - progressView.bar.alpha = 1 - progressView.setProgress(1, animated: true) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - UIView.animate(withDuration: 0.5, animations: { self.progressView.bar.alpha = 0 }) { finished in - self.progressView.progress = 0 - } - } - } - } - - /** - While progress is changed to 0.0, the bar will fade out. - */ - func cancelProgress() { - progressView.setProgress(0, animated: true) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - UIView.animate(withDuration: 0.5, animations: { - self.progressView.bar.alpha = 0 - }) - } - } - } - - func updateAccountRefreshProgressIndicator() { - - let progress = AccountManager.shared.combinedRefreshProgress - - if progress.isComplete { - if self.progress != 0 { - finishProgress() - } - } else { - let percent = Float(progress.numberCompleted) / Float(progress.numberOfTasks) - setProgress(percent, animated: true) {} - } - - } - -} diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index bfd636ccd..e9ca3bf07 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -718,15 +718,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { markArticlesWithUndo(articles, statusKey: .read, flag: true) } - func markAllAsRead() { - let accounts = AccountManager.shared.activeAccounts - var articles = Set
() - accounts.forEach { account in - articles.formUnion(account.fetchArticles(.unread)) - } - markAllAsRead(Array(articles)) - } - func markAllAsReadInTimeline() { markAllAsRead(articles) masterNavigationController.popViewController(animated: true) From bdd085345bdd42d3bf859f70d3ca2f825a35421b Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 25 Oct 2019 13:39:59 -0500 Subject: [PATCH 019/237] Change refreshed label color to be more subtle --- iOS/MasterFeed/RefreshProgressView.xib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/MasterFeed/RefreshProgressView.xib b/iOS/MasterFeed/RefreshProgressView.xib index 6fa3b68d0..f02ac45cc 100644 --- a/iOS/MasterFeed/RefreshProgressView.xib +++ b/iOS/MasterFeed/RefreshProgressView.xib @@ -22,7 +22,7 @@ From 74c9290e6afd17a0d52817b20dedbdd12b42b468 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 25 Oct 2019 14:57:01 -0500 Subject: [PATCH 020/237] Change disclosure image to be a filled triangle. Issue #1175 --- iOS/AppAssets.swift | 8 ++------ .../Cell/MasterFeedTableViewCell.swift | 2 +- .../MasterFeedTableViewSectionHeader.swift | 2 +- .../chevronSmall.imageset/Contents.json | 15 --------------- .../chevronSmall.imageset/chevron-small.pdf | Bin 4031 -> 0 bytes .../Contents.json | 2 +- .../disclosure.pdf} | Bin 4029 -> 3981 bytes 7 files changed, 5 insertions(+), 24 deletions(-) delete mode 100644 iOS/Resources/Assets.xcassets/chevronSmall.imageset/Contents.json delete mode 100644 iOS/Resources/Assets.xcassets/chevronSmall.imageset/chevron-small.pdf rename iOS/Resources/Assets.xcassets/{chevronBase.imageset => disclosure.imageset}/Contents.json (83%) rename iOS/Resources/Assets.xcassets/{chevronBase.imageset/chevron-base.pdf => disclosure.imageset/disclosure.pdf} (78%) diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index ca3806fd9..e054ec245 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -65,12 +65,8 @@ struct AppAssets { return UIImage(systemName: "circle")! }() - static var chevronSmallImage: UIImage = { - return UIImage(named: "chevronSmall")! - }() - - static var chevronBaseImage: UIImage = { - return UIImage(named: "chevronBase")! + static var disclosureImage: UIImage = { + return UIImage(named: "disclosure")! }() static var copyImage: UIImage = { diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift index 5dc3e3c3f..ea38faf93 100644 --- a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift +++ b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift @@ -178,7 +178,7 @@ private extension MasterFeedTableViewCell { func addDisclosureView() { disclosureButton = NonIntrinsicButton(type: .roundedRect) disclosureButton!.addTarget(self, action: #selector(buttonPressed(_:)), for: UIControl.Event.touchUpInside) - disclosureButton?.setImage(AppAssets.chevronBaseImage, for: .normal) + disclosureButton?.setImage(AppAssets.disclosureImage, for: .normal) addSubviewAtInit(disclosureButton!) } diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift index 07d6ab35c..6307efb8b 100644 --- a/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift +++ b/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift @@ -68,7 +68,7 @@ class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView { private var disclosureView: UIImageView = { let iView = NonIntrinsicImageView() iView.tintColor = UIColor.tertiaryLabel - iView.image = AppAssets.chevronSmallImage + iView.image = AppAssets.disclosureImage iView.contentMode = .center return iView }() diff --git a/iOS/Resources/Assets.xcassets/chevronSmall.imageset/Contents.json b/iOS/Resources/Assets.xcassets/chevronSmall.imageset/Contents.json deleted file mode 100644 index 6ffaf1016..000000000 --- a/iOS/Resources/Assets.xcassets/chevronSmall.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "chevron-small.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template" - } -} \ No newline at end of file diff --git a/iOS/Resources/Assets.xcassets/chevronSmall.imageset/chevron-small.pdf b/iOS/Resources/Assets.xcassets/chevronSmall.imageset/chevron-small.pdf deleted file mode 100644 index 7f9873a86ea8c6f5ec2d4baa9b856e8672bdd3fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4031 zcmai%XH*o~)`kg-PplY)vs zgJcngCL<~!k|axzC~~{i`Np~Te(Tn%Rds5g_w03Qzx&5?gbdU*C8ed{V4>ET^_j(j zjfcIh?O+4|1@LGm@WqP&L=Wd?Pjmo~l#mes(R6en;s}(t3yO$S$6@eT9H6KO_8<~) zC|9r#tx3GDTMLv`eOr+MVvx)!c57Hc#Z)1vPk`N?IFmxX3?$M5IPV z3vH?cVDWmY?vii12sGm+`GkRTB;82a>R!Q~`T{yyLB&UHiX(Zgi3(dMUHf0`{hBL8gQt?W2xM!6U z;aokR*(Q{~3}z4QZ>%x)eqgbgSYcf}c&z7cI~fWU=L(lBQ|KbmjTpqLWtB>`x7%>$ z`d;--Y4MW#FvFVVzQ>T~9gz*jxnX~nlro|^K`lS^4Ts55C#gf&FD6~}b;kh^1C;&u zCjsY11mHgmGs1b`JqZ|`2O#q!poVuNQszAXs>w!F^M2-|&QtSJO*bImF~&F|U`ZLO zYXa5)L=EqPCm6ed{C5@P3S~nO6>9UiQ91{JsN=jG zF*qYF)&Jd78Oh#PjYqk|$s}1)?{NV@Xp_&68R)-oi3?;Hp&evE7ZxJyrOu$qc}&a{ zd-Wx)R=5C0gOlaXZS)B9I)_1flcBoC%viKD^J*D#V?6z!!+c_N?)k{5{c6BSEsep~ z+n{Em8Ep_E+l+H%vB~QAiLTadx|1BVY-Y4CC++M&T081=gI`GsX=#O*+N-u!q=K+@ z?<5)xggu521LFi_)dY{wfHj6>F5VL6nW0NGu4?0j9fDqOCW*nqq+#6I%sHh3Q>_&) zTLrV?b)Bgx!qeG&<~_Xih|K6ZgYk5s$=qmAwpFUo2m9Qcj5yL&?R1)%ZP9^&odTfh}rR-XW95C*OWL5)`40&ObJi>NxZ1mEGQ2 zmGLfGT0F7k^5h>rdgRS&*1J%ipbC7l2Ca~}YnnscavA^fvib0=!*1i~#F<;h2MVwJ z_D$A=m&lna(V6BGE=#2l5;x73pvC>uRSiidG}W$0lxHSO^;ZF!bq1R1@dq1c!dz_P zTR*ajutY5_xp>fKIZKIulHJd`jep%3CcmB5wX$z|A)iMn_&04XI8I|GzaU>%y}O&0 zErpCd*ycE#dMWStXJ?DVgRGZ4YoVV^3_m3@heSBkgy`YwrC5JTlRYSc)SzLujEX?oQ+}>Fht4Oy= zJrx1t*)7&sEI}wof;V>}#HESu_&rAALogy(3Q8w&{V0DB+?{?Lpa}`O#6h1-FLjB| zQ5p0^Mez*{SVj5`t=AnVcbdUqzh2Ol+cNG)tU%XZ9(8A74boBuhc)R!X~%;dnQnjP z;L1@czQ>)%o~Am?ams?pS53u0j+4GkE#e+Wyz*|ZaN)`GcZ##s3=}qhBl6WUS>M@v z$k+=YjvUYigd9ty8$ca>L-Q(lW0@aFj@yXl!{3;)#I6!vEzUT@7{id9(tT_CQ_>8#Vm8|evD+R>Gjy_mX#@w z>EEE1F7UH;yyX4J?|!T1q!r8!Sq^&6J`!CQwAgZVQh3d${`@%S>TUKWkI(3v=NiC) z{DEgCV$ZfWirizi06E`gKbH&$x(Ht7;A5I&ItV$|j4%u?)F%prarm}cp~W8GeWOyZ z%`bc0CnR?^rFuNpocz{c^!9z*dMoUdbR)RG3p= zhhIZ=^oqRB3uRwrU$sX@M+@Bs&5BcNGc5E;>H#_d5Um7w%0|jmXIVl?oO9A*iocM4 z;z){jimgG{l$(r$b1O}=H7Bc^6+*Q&(sPo~f1uOR5$HP~k!^-T z_3^>+&yVXDDbH8-3q3l=r#kS+x4)+Pa`o?Tac`lgwPf@52NLGnHqU+{Z8GD8aF#Qo zGyF4rGgYsRjx&23G&%2$!nIw>8mqeQs zTdZe-AL1O!D0VH@AA~vyJE5FLo%V;MN@B20mUs6TTEgJCZluExj{+CVdXkQSD&C@)Tq-V=?#C2AY(%(tXuVs47Z6yR3Y< zK(bFUZ>&V23{zNTW}kbds^^4typ*M^%5$sd#@?#>x)3vnZH3`#wxVHHSyo=7R-zBR2+v`y(_nQi{BeNS(nW)@J%x}eUC4J{}Epjik%VD0~h+I>gvJ|e2$k2}0j_JH? z{AO_c>h)T3HJR0U)5jtDCvL_jkov6r#+cGPuXMZ6%f^ zj3pN(&YFvvGnlznFI4WeylqW)w#Ec>ggH%Ns(SLrW}lW7Nlth*E(cslx2_!HuDeo4 z-eTJl*caZHW>g6G2`8~_zae^w)`bKN9b5b~S<|x?J;f;EFY;2yhp6(XiqK+ zE6-=1LCKSZz8jM@{De3utW>nEk9+o@$-AT}i^aX}%Of#!UahXJV{a5j4{G+s*>&0D zw4yo^3liq|$SgL0q*PpsyH@c$ejB~&v|6C}yJCc5jN*v~uZEHU$PVg&?!aqrY-8l} z+}_B(K1dE!%KQnO2hs$YGAgpbn2$4ff?Aqro9b?#h4kABS`Bj3seV=S3cbjTWVPnn z^EfFbfhZMT@~tuPx#i;xl0<8$tF_i$ihb4pN-HXcTbJ8gvs0^0V@T_=hQCINhHi?P zgn8ApX0$~#kgvdf_7s=DpckQNyMIhKd}`?S1nImwNY`-j_?!g7<~)n6p$lJsG#x!ezZS>^?TTIh{W3)^)mbqjUcj z(hlkEY2V=WE$nL(a>bdgEOfZs>fMVg-quaElV2JZV_gB)OQWB=G`nJOrzYGY#|I4` z8df%3cc@yOZC8DvYB))f?6G8XX>;LnSst^iDsy_5B4E?^1>N;A@}s`c`}oBP^MGr+ zW8GUJ%cMtJkM_MkcrkguADge;FiS(5+l=;>f3Pl_>0ZxSe_nTOslgOwdE+{}V=Okd z8dms(q*LLfHB_*Ww-C%1n?6fg@PEFX@wVgLNaKWS-~eq5eH)|V8BYHCz*pM_&SfgD zZK(H?r7h&eoQ2v(`#{@3_2o-zab&Gftw$Zrv7SR021jHIWfNtZW8&K%Pu=LiAAIHgu(&FHPP^s3kPy5zVXt_Sz!v%GJutG0)(MMdz0|9*|ASX({apHW^} zKHe3aQem_9f>891wExv4rNq8m-ugm7MgVC!>>y_NOkHNO?b0pB`KPO{qhNknyeBfSJ388K>x@j_j>SHTWOeyjPeH7(eJ^EMm7&>h_a6a5%s$P34Gy z_0MM(E0rA#?TpwJkv5kej$9{K{b3&)R-*=hDu2z0V`2ePe&IlAT6(gl!c?L!~jE26oKdmP!Rq*_4^RTDCAB# zsSzoQ)F~Ja7FGWp29=hF%EM%(p>PCJ)&dF@q1+VRWjvND0seo<-+S~S;OxMZGDw4= z|91g0NF*Ey*a1IdaD+VN->Dbi_E!vwKu{|D&lntzq?q;37!(Sll=9zV2pHuA^KUUE z?7!suZ#sEOS^v2f4u}3nJ~#sL%btk@l%ord@cn>f?C6K1{4W48!s989Q(dK;j9+&^cT(JvQrFd`pa##3GIKO~K^bK8|o%SVjE~R+?Ka;F- zwnx5XA-kqIzpUpWvnw%O?-~-L4$oguw9|n@pjk^oJ1s>QkNs51U0GD4k z%kGI*)l(#SWiG$6^4xZcQAk(ywSxDiZ&{0W9h>6XAlhV5H(@t;Ml-=?$Y6zo(pp>}7@nq+xQx6_5=~im~wW0FxtgpXrf8Spp`O@qC7uL!P>U;O)dcAgY z+QgBoy;owoS5o}p_BUzoe;4bU{7Tq9Iq^{RuCsN0FQ3kgd)cgae}hI|6IW_pN^wb1 zYGN*km7kQwWvXDH0HTe|EjMpuoXpK?Xr^FjHrbQ6huO@)X!1>7Z7xFtBU3|5Q)3g8 z$$Wg0$Xr7{ODz*a1p^RJ$W!0~GYkxjEltqHjExNqCQsp0O*b|N+K8s!#2iz-5f*i( zCPwIbjg3q(%riE&G@Pu!Z|9t1oNS(G0CasC&_!m+#s*1=i5AIbCPtgw;t1puI(*lhp+ delta 666 zcmeB`-zz^Mpgwm}tltp>o|gO7T=5M8|Bux@W3ef0;9|Md#8O?~=~=!#R^^IU?*6{H zA64#u@wxxxtg`FWs>ex!{J&Uy#GmbGzPO{`oI<6PZ`WYLMJEJ$A~d|uH%W~50BBU znx?02kS02V$vcc^H$O+v3sHAI>&TN)y@ou~)~`A`t9#ck6*Co?S<)w`X~ujgIKSxn zGA?gp#dFK7A}(ldJ>TJYV-5e6joaqseW*PBuqDo1_Kd`~2DRO#*~->7_kKDZndE;-51Sr^UwLbuKa!X<(^B- zeMdOBpSx~2B=$jFYrXXT&6f|#8D#h`>HXPPwCnI`>%A90>+kLBV&_WDODQfXN=?iK zvGS9$xJ(rc6hO4Gkr_1tECQI@5u$Y<~Cr)0;tIcI#X=H9_ zU~Fk(F!>&@Br=zg&r-|6Lcst86!H|fzzhQe6B9#pF=Jy>^T{cEs_7<1M(A<|rbbxQ zV^e2giLTe!*Z{*kV-sVG$shRaoXri*42+Y^EsPD!3{8?#jg!q%EYlJ#&COF%3=Iv8 Z%gw;t1pvaC@GAfS From cf7ea99cb6b18dcf68812b2e3151d1d6cb642997 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 25 Oct 2019 15:03:13 -0500 Subject: [PATCH 021/237] Delete timeline unread count animation. Issue #1174 --- .../MasterTimelineViewController.swift | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index d27e14a20..b9c1c906f 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -453,7 +453,7 @@ private extension MasterTimelineViewController { } titleView.label.text = coordinator.timelineName - updateTitleUnreadCount(animate: false) + updateTitleUnreadCount() if coordinator.timelineFetcher is Feed { titleView.heightAnchor.constraint(equalToConstant: 44.0).isActive = true @@ -475,7 +475,7 @@ private extension MasterTimelineViewController { } func updateUI() { - updateTitleUnreadCount(animate: true) + updateTitleUnreadCount() updateToolbar() } @@ -484,17 +484,9 @@ private extension MasterTimelineViewController { firstUnreadButton.isEnabled = coordinator.isTimelineUnreadAvailable } - func updateTitleUnreadCount(animate: Bool) { + func updateTitleUnreadCount() { if let unreadCountProvider = coordinator.timelineFetcher as? UnreadCountProvider { - if animate { - UIView.animate(withDuration: 0.3) { - self.titleView?.unreadCountView.unreadCount = unreadCountProvider.unreadCount - self.titleView?.setNeedsLayout() - self.titleView?.layoutIfNeeded() - } - } else { - self.titleView?.unreadCountView.unreadCount = unreadCountProvider.unreadCount - } + self.titleView?.unreadCountView.unreadCount = unreadCountProvider.unreadCount } } From 21aaf71cbf8066886e70be6769bd519331f32b0b Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 25 Oct 2019 15:52:32 -0500 Subject: [PATCH 022/237] Update timeline theme. Issue #1169 --- iOS/AppAssets.swift | 4 +++ ...asterTimelineAccessibilityCellLayout.swift | 2 +- .../MasterTimelineDefaultCellLayout.swift | 2 +- .../Cell/MasterTimelineTableViewCell.swift | 15 ++++++++- .../Contents.json | 33 +++++++++++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 iOS/Resources/Assets.xcassets/timelineBackgroundColor.colorset/Contents.json diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index e054ec245..77219db2c 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -157,6 +157,10 @@ struct AppAssets { return UIImage(systemName: "star.fill")! }() + static var timelineBackgroundColor: UIColor = { + return UIColor(named: "timelineBackgroundColor")! + }() + static var timelineStarImage: UIImage = { let image = UIImage(systemName: "star.fill")! return image.withTintColor(AppAssets.starColor, renderingMode: .alwaysOriginal) diff --git a/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift b/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift index 31e8c5bc6..9640660ae 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift @@ -35,7 +35,7 @@ struct MasterTimelineAccessibilityCellLayout: MasterTimelineCellLayout { currentPoint.x += MasterTimelineDefaultCellLayout.unreadCircleDimension + MasterTimelineDefaultCellLayout.unreadCircleMarginRight // Separator Insets - self.separatorInsets = UIEdgeInsets(top: 0, left: currentPoint.x, bottom: 0, right: 0) + self.separatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) // Avatar if cellData.showAvatar { diff --git a/iOS/MasterTimeline/Cell/MasterTimelineDefaultCellLayout.swift b/iOS/MasterTimeline/Cell/MasterTimelineDefaultCellLayout.swift index f7ca315c5..6e9d82a4a 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineDefaultCellLayout.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineDefaultCellLayout.swift @@ -68,7 +68,7 @@ struct MasterTimelineDefaultCellLayout: MasterTimelineCellLayout { currentPoint.x += MasterTimelineDefaultCellLayout.unreadCircleDimension + MasterTimelineDefaultCellLayout.unreadCircleMarginRight // Separator Insets - self.separatorInsets = UIEdgeInsets(top: 0, left: currentPoint.x, bottom: 0, right: 0) + self.separatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) // Avatar if cellData.showAvatar { diff --git a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift index 0a240370e..63249d581 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift @@ -23,6 +23,12 @@ class MasterTimelineTableViewCell: VibrantTableViewCell { return NonIntrinsicImageView(image: AppAssets.timelineStarImage) }() + private let separatorAccent: UIView = { + let view = UIView() + view.backgroundColor = UIColor.white + return view + }() + var cellData: MasterTimelineCellData! { didSet { updateSubviews() @@ -43,6 +49,8 @@ class MasterTimelineTableViewCell: VibrantTableViewCell { summaryView.highlightedTextColor = highlightedTextColor dateView.highlightedTextColor = highlightedTextColor feedNameView.highlightedTextColor = highlightedTextColor + + backgroundColor = AppAssets.timelineBackgroundColor } override var frame: CGRect { @@ -82,6 +90,10 @@ class MasterTimelineTableViewCell: VibrantTableViewCell { separatorInset = layout.separatorInsets + if traitCollection.userInterfaceStyle == .light { + let separatorAccentRect = CGRect(x: safeAreaInsets.left, y: frame.height - 2, width: frame.width - safeAreaInsets.right - safeAreaInsets.left, height: 1) + separatorAccent.setFrameIfNotEqual(separatorAccentRect) + } } func setAvatarImage(_ image: UIImage) { @@ -137,7 +149,8 @@ private extension MasterTimelineTableViewCell { addSubviewAtInit(feedNameView, hidden: true) addSubviewAtInit(avatarView, hidden: true) addSubviewAtInit(starView, hidden: true) - + addSubviewAtInit(separatorAccent, hidden: false) + } func updatedLayout(width: CGFloat) -> MasterTimelineCellLayout { diff --git a/iOS/Resources/Assets.xcassets/timelineBackgroundColor.colorset/Contents.json b/iOS/Resources/Assets.xcassets/timelineBackgroundColor.colorset/Contents.json new file mode 100644 index 000000000..2cc5212b8 --- /dev/null +++ b/iOS/Resources/Assets.xcassets/timelineBackgroundColor.colorset/Contents.json @@ -0,0 +1,33 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "247", + "alpha" : "1.000", + "blue" : "247", + "green" : "247" + } + } + }, + { + "idiom" : "universal", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "platform" : "ios", + "reference" : "systemBackgroundColor" + } + } + ] +} \ No newline at end of file From e1a571fab3461d68d48d00fd7c358540ffc1085a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 25 Oct 2019 15:56:09 -0500 Subject: [PATCH 023/237] Remove separator accent when switching to darkmode --- iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift index 63249d581..9f9d1c342 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift @@ -93,6 +93,8 @@ class MasterTimelineTableViewCell: VibrantTableViewCell { if traitCollection.userInterfaceStyle == .light { let separatorAccentRect = CGRect(x: safeAreaInsets.left, y: frame.height - 2, width: frame.width - safeAreaInsets.right - safeAreaInsets.left, height: 1) separatorAccent.setFrameIfNotEqual(separatorAccentRect) + } else { + separatorAccent.setFrameIfNotEqual(.zero) } } From 8304b174bfc2f0c8859d60c1e4540dc3268377fa Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 25 Oct 2019 16:27:10 -0500 Subject: [PATCH 024/237] Change refresh label to use "Updated" instead of "Refreshed" --- iOS/MasterFeed/RefreshProgressView.swift | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/iOS/MasterFeed/RefreshProgressView.swift b/iOS/MasterFeed/RefreshProgressView.swift index a153ada73..683a24bc8 100644 --- a/iOS/MasterFeed/RefreshProgressView.swift +++ b/iOS/MasterFeed/RefreshProgressView.swift @@ -30,16 +30,21 @@ class RefreshProgressView: UIView { } func updateRefreshLabel() { - if let refreshDate = AppDefaults.lastRefresh { - let relativeDateTimeFormatter = RelativeDateTimeFormatter() - relativeDateTimeFormatter.dateTimeStyle = .named - let refreshed = relativeDateTimeFormatter.localizedString(for: refreshDate, relativeTo: Date()) - let localizedRefreshText = NSLocalizedString("Refreshed %@", comment: "Refreshed") - let refreshText = NSString.localizedStringWithFormat(localizedRefreshText as NSString, refreshed) as String - label.text = refreshText + if let lastRefresh = AppDefaults.lastRefresh { + if Date() > lastRefresh.addingTimeInterval(1) { + let relativeDateTimeFormatter = RelativeDateTimeFormatter() + relativeDateTimeFormatter.dateTimeStyle = .named + let refreshed = relativeDateTimeFormatter.localizedString(for: lastRefresh, relativeTo: Date()) + let localizedRefreshText = NSLocalizedString("Updated %@", comment: "Updated") + let refreshText = NSString.localizedStringWithFormat(localizedRefreshText as NSString, refreshed) as String + label.text = refreshText + } else { + label.text = NSLocalizedString("Updated just now", comment: "Updated Just Now") + } } } + @objc func progressDidChange(_ note: Notification) { let progress = AccountManager.shared.combinedRefreshProgress From 473053e8137bb928aa22cdcc2a47d224e6df5f71 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 25 Oct 2019 17:07:40 -0500 Subject: [PATCH 025/237] Update the updated since label on every appearance --- iOS/MasterFeed/MasterFeedViewController.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index c0c606a96..3759b8fe6 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -60,22 +60,16 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged) configureToolbar() - - updateUI() becomeFirstResponder() - } override func viewWillAppear(_ animated: Bool) { navigationController?.title = NSLocalizedString("Feeds", comment: "Feeds") applyChanges(animate: false) + updateUI() super.viewWillAppear(animated) } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } - // MARK: Notifications @objc func unreadCountDidChange(_ note: Notification) { From d1987c650ec20a6cbb95a0a4dfc76ca6a88e1c70 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 26 Oct 2019 11:09:34 -0700 Subject: [PATCH 026/237] Roll back the timeline background color change. Remove the white accent view. --- .../Cell/MasterTimelineTableViewCell.swift | 15 --------------- .../Contents.json | 9 ++------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift index 9f9d1c342..91669f2de 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift @@ -23,12 +23,6 @@ class MasterTimelineTableViewCell: VibrantTableViewCell { return NonIntrinsicImageView(image: AppAssets.timelineStarImage) }() - private let separatorAccent: UIView = { - let view = UIView() - view.backgroundColor = UIColor.white - return view - }() - var cellData: MasterTimelineCellData! { didSet { updateSubviews() @@ -89,13 +83,6 @@ class MasterTimelineTableViewCell: VibrantTableViewCell { dateView.setFrameIfNotEqual(layout.dateRect) separatorInset = layout.separatorInsets - - if traitCollection.userInterfaceStyle == .light { - let separatorAccentRect = CGRect(x: safeAreaInsets.left, y: frame.height - 2, width: frame.width - safeAreaInsets.right - safeAreaInsets.left, height: 1) - separatorAccent.setFrameIfNotEqual(separatorAccentRect) - } else { - separatorAccent.setFrameIfNotEqual(.zero) - } } func setAvatarImage(_ image: UIImage) { @@ -151,8 +138,6 @@ private extension MasterTimelineTableViewCell { addSubviewAtInit(feedNameView, hidden: true) addSubviewAtInit(avatarView, hidden: true) addSubviewAtInit(starView, hidden: true) - addSubviewAtInit(separatorAccent, hidden: false) - } func updatedLayout(width: CGFloat) -> MasterTimelineCellLayout { diff --git a/iOS/Resources/Assets.xcassets/timelineBackgroundColor.colorset/Contents.json b/iOS/Resources/Assets.xcassets/timelineBackgroundColor.colorset/Contents.json index 2cc5212b8..702421056 100644 --- a/iOS/Resources/Assets.xcassets/timelineBackgroundColor.colorset/Contents.json +++ b/iOS/Resources/Assets.xcassets/timelineBackgroundColor.colorset/Contents.json @@ -7,13 +7,8 @@ { "idiom" : "universal", "color" : { - "color-space" : "srgb", - "components" : { - "red" : "247", - "alpha" : "1.000", - "blue" : "247", - "green" : "247" - } + "platform" : "ios", + "reference" : "systemBackgroundColor" } }, { From 6403808a873efbce6813c6ff14afa2d30a1095ae Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 26 Oct 2019 11:13:49 -0700 Subject: [PATCH 027/237] Bump build number to 3. --- xcconfig/common/NetNewsWire_ios_target_common.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index 465a72ef3..79dd814a9 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it MARKETING_VERSION = 5.0 -CURRENT_PROJECT_VERSION = 2 +CURRENT_PROJECT_VERSION = 3 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon From 4c5b184976f4888b5f39fd179039a950a888cb09 Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Sat, 26 Oct 2019 14:19:35 -0400 Subject: [PATCH 028/237] Bump deployment target to 10.15 for Mac --- xcconfig/NetNewsWire_project.xcconfig | 2 +- xcconfig/common/NetNewsWire_mac_target_common.xcconfig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xcconfig/NetNewsWire_project.xcconfig b/xcconfig/NetNewsWire_project.xcconfig index e093d1156..7450fcc47 100644 --- a/xcconfig/NetNewsWire_project.xcconfig +++ b/xcconfig/NetNewsWire_project.xcconfig @@ -39,7 +39,7 @@ GCC_WARN_UNDECLARED_SELECTOR = YES GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE GCC_WARN_UNUSED_FUNCTION = YES GCC_WARN_UNUSED_VARIABLE = YES -MACOSX_DEPLOYMENT_TARGET = 10.14.4 +MACOSX_DEPLOYMENT_TARGET = 10.15 IPHONEOS_DEPLOYMENT_TARGET = 13.0 //SDKROOT = macosx SWIFT_SWIFT3_OBJC_INFERENCE = Off diff --git a/xcconfig/common/NetNewsWire_mac_target_common.xcconfig b/xcconfig/common/NetNewsWire_mac_target_common.xcconfig index 081ef96e7..74c6e10e2 100644 --- a/xcconfig/common/NetNewsWire_mac_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_mac_target_common.xcconfig @@ -5,4 +5,4 @@ CURRENT_PROJECT_VERSION = 2612 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon COMBINE_HIDPI_IMAGES = YES -MACOSX_DEPLOYMENT_TARGET = 10.14.4 +MACOSX_DEPLOYMENT_TARGET = 10.15 From 1fd55ab846eeee69db078b5f41c9ade9d4125e93 Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Sat, 26 Oct 2019 14:21:00 -0400 Subject: [PATCH 029/237] Remove an unused outlet for enableWebInspectorMenuItem --- Mac/Base.lproj/Main.storyboard | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Mac/Base.lproj/Main.storyboard b/Mac/Base.lproj/Main.storyboard index ba4a9a4de..a077b7153 100644 --- a/Mac/Base.lproj/Main.storyboard +++ b/Mac/Base.lproj/Main.storyboard @@ -1,8 +1,7 @@ - + - - + @@ -606,7 +605,6 @@ - From 2903e4507b85614bbc6a7f4100cc734557dcdc6a Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Sat, 26 Oct 2019 14:23:44 -0400 Subject: [PATCH 030/237] Remove references to WebKit preferences that are not pertinent in 10.15. --- Mac/MainWindow/Detail/DetailWebViewController.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 9f2c08b12..4ebf7a697 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -61,9 +61,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { let preferences = WKPreferences() preferences.minimumFontSize = 12.0 preferences.javaScriptCanOpenWindowsAutomatically = false - preferences.javaEnabled = false preferences.javaScriptEnabled = true - preferences.plugInsEnabled = false let configuration = WKWebViewConfiguration() configuration.preferences = preferences From 030d86c1b7a2baf28387b5bb6d608d2a847d7683 Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Sat, 26 Oct 2019 14:42:11 -0400 Subject: [PATCH 031/237] Use the NSCachesDirectory default location, which for sandboxed builds will end up inside our sandbox container. --- Mac/AppDelegate.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 52eedd75e..e5a678c01 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -163,9 +163,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } } - let tempDirectory = NSTemporaryDirectory() - let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String) - let cacheFolder = (tempDirectory as NSString).appendingPathComponent(bundleIdentifier) + // Try to establish a cache in the Caches folder, but if it fails for some reason fall back to a temporary dir + let cacheFolder: String + if let userCacheFolder = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false).path { + cacheFolder = userCacheFolder + } + else { + let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String) + cacheFolder = (NSTemporaryDirectory() as NSString).appendingPathComponent(bundleIdentifier) + } // If the image disk cache hasn't been flushed for 3 days and the network is available, delete it if let flushDate = AppDefaults.lastImageCacheFlushDate, flushDate.addingTimeInterval(3600*24*3) < Date() { From 14fbf612c11d280aae3e647b4fb315274efcf91d Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 26 Oct 2019 20:58:40 -0700 Subject: [PATCH 032/237] Set up 5.1d1 testing things: new appcast URL, build # 3000. --- Appcasts/netnewswire-5.1-beta.xml | 21 +++++++++++++++++++ Mac/Resources/Info.plist | 4 ++-- .../NetNewsWire_mac_target_common.xcconfig | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100755 Appcasts/netnewswire-5.1-beta.xml diff --git a/Appcasts/netnewswire-5.1-beta.xml b/Appcasts/netnewswire-5.1-beta.xml new file mode 100755 index 000000000..6e1fbc704 --- /dev/null +++ b/Appcasts/netnewswire-5.1-beta.xml @@ -0,0 +1,21 @@ + + + +NetNewsWire Betas +https://ranchero.com/downloads/netnewswire-beta.xml +Most recent NetNewsWire changes with links to updates. +en + + + NetNewsWire 5.0.3 + Same as 5.0.3b2 — just bumped the version number to 5.0.3.

+ ]]> +
+ Tue, 22 Oct 2019 13:00:00 -0700 + + 10.14.4 +
+ +
+
diff --git a/Mac/Resources/Info.plist b/Mac/Resources/Info.plist index a9c54ee1d..d761dde8e 100644 --- a/Mac/Resources/Info.plist +++ b/Mac/Resources/Info.plist @@ -60,9 +60,9 @@ OSAScriptingDefinition NetNewsWire.sdef SUFeedURL - https://ranchero.com/downloads/netnewswire-release.xml + https://ranchero.com/downloads/netnewswire-5.1-beta.xml FeedURLForTestBuilds - https://ranchero.com/downloads/netnewswire-beta.xml + https://ranchero.com/downloads/netnewswire-5.1-beta.xml UserAgent NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/) diff --git a/xcconfig/common/NetNewsWire_mac_target_common.xcconfig b/xcconfig/common/NetNewsWire_mac_target_common.xcconfig index 74c6e10e2..6ebf473bc 100644 --- a/xcconfig/common/NetNewsWire_mac_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_mac_target_common.xcconfig @@ -1,6 +1,6 @@ // High Level Settings common to both the Mac application and any extensions we bundle with it MARKETING_VERSION = 5.1d1 -CURRENT_PROJECT_VERSION = 2612 +CURRENT_PROJECT_VERSION = 3000 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon From 0581d94935658351605caaa7983fe122e0ffcdcc Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 26 Oct 2019 21:19:37 -0700 Subject: [PATCH 033/237] Add a little padding, left and right, to the unread count view in the Feeds list. --- iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift b/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift index 09b1d5d69..733187341 100644 --- a/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift +++ b/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift @@ -11,7 +11,7 @@ import UIKit class MasterFeedUnreadCountView : UIView { var padding: UIEdgeInsets { - return UIEdgeInsets(top: 1.0, left: 7.0, bottom: 1.0, right: 7.0) + return UIEdgeInsets(top: 1.0, left: 9.0, bottom: 1.0, right: 9.0) } let cornerRadius = 8.0 From 596d3e110113a2414bcd76f2b31a6a08e08c268f Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 26 Oct 2019 21:30:44 -0700 Subject: [PATCH 034/237] Make the unread count color in the Feeds list a little lighter. Set up the colors as an app asset. --- iOS/AppAssets.swift | 4 +++ .../Cell/MasterFeedUnreadCountView.swift | 2 +- .../Contents.json | 28 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 iOS/Resources/Assets.xcassets/unreadCountBackgroundColor.colorset/Contents.json diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 77219db2c..88d50df7a 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -182,6 +182,10 @@ struct AppAssets { return UIColor(named: "vibrantTextColor")! }() + static var unreadCountBackgroundColor: UIColor = { + return UIColor(named: "unreadCountBackgroundColor")! + }() + static func image(for accountType: AccountType) -> UIImage? { switch accountType { case .onMyMac: diff --git a/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift b/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift index 733187341..a5093a6bb 100644 --- a/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift +++ b/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift @@ -15,7 +15,7 @@ class MasterFeedUnreadCountView : UIView { } let cornerRadius = 8.0 - let bgColor = UIColor.darkGray + let bgColor = AppAssets.unreadCountBackgroundColor var textColor: UIColor { return UIColor.white } diff --git a/iOS/Resources/Assets.xcassets/unreadCountBackgroundColor.colorset/Contents.json b/iOS/Resources/Assets.xcassets/unreadCountBackgroundColor.colorset/Contents.json new file mode 100644 index 000000000..fab921c40 --- /dev/null +++ b/iOS/Resources/Assets.xcassets/unreadCountBackgroundColor.colorset/Contents.json @@ -0,0 +1,28 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "platform" : "ios", + "reference" : "systemGray2Color" + } + }, + { + "idiom" : "universal", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "platform" : "ios", + "reference" : "systemGray2Color" + } + } + ] +} \ No newline at end of file From cc1da562b1de0cf4d3216350479fdd2b687788ca Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Sun, 27 Oct 2019 12:06:54 -0400 Subject: [PATCH 035/237] Add commands to the Sparkle build phase script to remove Autoupdater and Updater.app binaries. These are not needed by NetNewsWire since we use the XPC services to provide the same functionality. --- NetNewsWire.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 4b3207918..79a289099 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -3628,7 +3628,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Remove unused Sparkle components\n\n# Sign XPC Helpers\ncodesign --verbose --entitlements \"${PROJECT_DIR}/submodules/Sparkle/Downloader/org.sparkle-project.Downloader.entitlements\" --force -o runtime --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.Downloader.xpc\"\ncodesign --verbose --force -o runtime --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerLauncher.xpc\"\ncodesign --verbose --force -o runtime --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerConnection.xpc\"\ncodesign --verbose --force -o runtime --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerStatus.xpc\"\n"; + shellScript = "# Remove unused Sparkle components\nSPARKLE_DIR=\"${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}/Contents/Frameworks/Sparkle.framework\"\nfind ${SPARKLE_DIR} -name Updater.app -delete\nfind ${SPARKLE_DIR} -name Autoupdate -delete\n\n# Sign XPC Helpers\ncodesign --verbose --entitlements \"${PROJECT_DIR}/submodules/Sparkle/Downloader/org.sparkle-project.Downloader.entitlements\" --force -o runtime --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.Downloader.xpc\"\ncodesign --verbose --force -o runtime --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerLauncher.xpc\"\ncodesign --verbose --force -o runtime --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerConnection.xpc\"\ncodesign --verbose --force -o runtime --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerStatus.xpc\"\n"; }; 84C987A52000AC9E0066B150 /* Run Script: Automated build numbers */ = { isa = PBXShellScriptBuildPhase; From 9c946758bf15653b07166368c018b3735c51b67a Mon Sep 17 00:00:00 2001 From: Nate Weaver Date: Sun, 27 Oct 2019 11:38:41 -0500 Subject: [PATCH 036/237] Update Protocols and Delegates section --- Technotes/CodingGuidelines.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Technotes/CodingGuidelines.md b/Technotes/CodingGuidelines.md index a9014acf2..4795c1c01 100644 --- a/Technotes/CodingGuidelines.md +++ b/Technotes/CodingGuidelines.md @@ -66,6 +66,10 @@ Consider this a hard rule: all Swift classes must be marked as `final`, and all Protocols and delegates (which are also protocol-conforming) are preferred. +Protocol conformance should be implemented in Swift extensions. + +If a delegate protocol defined in the same file as the delegator class or struct, the protocol interface should be specified before the delegator. + Default implementations in protocols are allowed but ever-so-slightly discouraged. You’ll find several instances in the code, but this is done carefully — we don’t want this to be just another form of inheritance, where you find that you have to bounce back-and-forth between files to figure out what’s going on. There is one unfortunate case about protocols to note: in Swift you can’t create a Set of some protocol-conforming objects, and we use sets frequently. In those situations another solution — such as a thin object with a delegate — might be better. From 25411ead452f2b8a6d8545e751fc7a90c0afe31f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 28 Oct 2019 00:51:13 -0400 Subject: [PATCH 037/237] Add a highlight background color Adds an IBInspectable background color for the hightlight state. --- iOS/UIKit Extensions/VibrantButton.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOS/UIKit Extensions/VibrantButton.swift b/iOS/UIKit Extensions/VibrantButton.swift index dc311d549..51a4a7021 100644 --- a/iOS/UIKit Extensions/VibrantButton.swift +++ b/iOS/UIKit Extensions/VibrantButton.swift @@ -9,6 +9,8 @@ import UIKit class VibrantButton: UIButton { + + @IBInspectable var backgroundHighlightColor: UIColor = AppAssets.secondaryAccentColor override init(frame: CGRect) { super.init(frame: frame) @@ -25,7 +27,7 @@ class VibrantButton: UIButton { override var isHighlighted: Bool { didSet { - backgroundColor = isHighlighted ? AppAssets.secondaryAccentColor : nil + backgroundColor = isHighlighted ? backgroundHighlightColor : nil titleLabel?.alpha = 1 } } From 6e973fb5ed2eb816a623b9664f850ca753a16fca Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 28 Oct 2019 00:53:09 -0400 Subject: [PATCH 038/237] Set highlight background colors for "Delete Account" This matches the colors from the "Contacts" app --- iOS/Inspector/Inspector.storyboard | 18 ++++++++- .../Contents.json | 38 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 iOS/Resources/Assets.xcassets/deleteBackgroundColor.colorset/Contents.json diff --git a/iOS/Inspector/Inspector.storyboard b/iOS/Inspector/Inspector.storyboard index e87fb1179..c5dfeee7e 100644 --- a/iOS/Inspector/Inspector.storyboard +++ b/iOS/Inspector/Inspector.storyboard @@ -1,8 +1,9 @@ - + - + + @@ -118,6 +119,14 @@ + + + + + + + + @@ -166,4 +175,9 @@ + + + + + diff --git a/iOS/Resources/Assets.xcassets/deleteBackgroundColor.colorset/Contents.json b/iOS/Resources/Assets.xcassets/deleteBackgroundColor.colorset/Contents.json new file mode 100644 index 000000000..1af5a3916 --- /dev/null +++ b/iOS/Resources/Assets.xcassets/deleteBackgroundColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0xD1", + "alpha" : "1.000", + "blue" : "0xD6", + "green" : "0xD1" + } + } + }, + { + "idiom" : "universal", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0x3A", + "alpha" : "1.000", + "blue" : "0x3C", + "green" : "0x3A" + } + } + } + ] +} \ No newline at end of file From 7682825469e55a5415bc00dfbaacb5b62b4535fe Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 28 Oct 2019 01:44:09 -0400 Subject: [PATCH 039/237] Align feed icon with first line of label Image was aligned with top padding, takes into consideration the image and font size (line height). --- iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift index b60177efb..4066772dd 100644 --- a/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift +++ b/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift @@ -54,7 +54,9 @@ struct MasterFeedTableViewCellLayout { var rFavicon = CGRect.zero if !shouldShowDisclosure { let x = bounds.origin.x + ((MasterFeedTableViewCellLayout.disclosureButtonSize.width - MasterFeedTableViewCellLayout.imageSize.width) / 2) - let y = UIFontMetrics.default.scaledValue(for: MasterFeedTableViewCellLayout.verticalPadding) + let y = UIFontMetrics.default.scaledValue(for: MasterFeedTableViewCellLayout.verticalPadding) + + label.font.lineHeight / 2.0 - + MasterFeedTableViewCellLayout.imageSize.height / 2.0 rFavicon = CGRect(x: x, y: y, width: MasterFeedTableViewCellLayout.imageSize.width, height: MasterFeedTableViewCellLayout.imageSize.height) } From 6269dd3def7eafa2c22d0f3c40a3374f9e47a490 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 28 Oct 2019 13:05:23 -0500 Subject: [PATCH 040/237] Set delete highlight color to red. --- iOS/Inspector/Inspector.storyboard | 6 +++--- .../Contents.json | 18 ++++-------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/iOS/Inspector/Inspector.storyboard b/iOS/Inspector/Inspector.storyboard index c5dfeee7e..e87d45987 100644 --- a/iOS/Inspector/Inspector.storyboard +++ b/iOS/Inspector/Inspector.storyboard @@ -1,8 +1,8 @@ - + - + @@ -177,7 +177,7 @@
- +
diff --git a/iOS/Resources/Assets.xcassets/deleteBackgroundColor.colorset/Contents.json b/iOS/Resources/Assets.xcassets/deleteBackgroundColor.colorset/Contents.json index 1af5a3916..838683920 100644 --- a/iOS/Resources/Assets.xcassets/deleteBackgroundColor.colorset/Contents.json +++ b/iOS/Resources/Assets.xcassets/deleteBackgroundColor.colorset/Contents.json @@ -7,13 +7,8 @@ { "idiom" : "universal", "color" : { - "color-space" : "srgb", - "components" : { - "red" : "0xD1", - "alpha" : "1.000", - "blue" : "0xD6", - "green" : "0xD1" - } + "platform" : "ios", + "reference" : "systemRedColor" } }, { @@ -25,13 +20,8 @@ } ], "color" : { - "color-space" : "srgb", - "components" : { - "red" : "0x3A", - "alpha" : "1.000", - "blue" : "0x3C", - "green" : "0x3A" - } + "platform" : "ios", + "reference" : "systemRedColor" } } ] From 729b768547efb98dae0f47d26b3ccbf3efb8f012 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 28 Oct 2019 11:11:16 -0700 Subject: [PATCH 041/237] Update CodingGuidelines.md --- Technotes/CodingGuidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Technotes/CodingGuidelines.md b/Technotes/CodingGuidelines.md index 4795c1c01..e7c11cdea 100644 --- a/Technotes/CodingGuidelines.md +++ b/Technotes/CodingGuidelines.md @@ -68,7 +68,7 @@ Protocols and delegates (which are also protocol-conforming) are preferred. Protocol conformance should be implemented in Swift extensions. -If a delegate protocol defined in the same file as the delegator class or struct, the protocol interface should be specified before the delegator. +If a delegate protocol is defined in the same file as the delegator class or struct, the protocol interface should be specified before the delegator. Default implementations in protocols are allowed but ever-so-slightly discouraged. You’ll find several instances in the code, but this is done carefully — we don’t want this to be just another form of inheritance, where you find that you have to bounce back-and-forth between files to figure out what’s going on. From b0d40afa3c7409bbd5e3add4b1cf69d009235593 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 28 Oct 2019 13:39:34 -0500 Subject: [PATCH 042/237] Don't compile testURLsOfCurrentArticle. --- NetNewsWire.xcodeproj/project.pbxproj | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 79a289099..96ead59fc 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -293,7 +293,7 @@ 65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */; }; - 65ED3FEB235DEF6C0081F399 /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */; }; + 65ED3FEB235DEF6C0081F399 /* (null) in Sources */ = {isa = PBXBuildFile; }; 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; @@ -406,7 +406,7 @@ 65ED405D235DEF6C0081F399 /* SidebarKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */; }; 65ED405E235DEF6C0081F399 /* DefaultFeeds.opml in Resources */ = {isa = PBXBuildFile; fileRef = 84A3EE52223B667F00557320 /* DefaultFeeds.opml */; }; 65ED405F235DEF6C0081F399 /* Preferences.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8022629E4800D921D6 /* Preferences.storyboard */; }; - 65ED4060235DEF6C0081F399 /* ExportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA3C227A37AF00D19003 /* ExportOPMLSheet.xib */; }; + 65ED4060235DEF6C0081F399 /* (null) in Resources */ = {isa = PBXBuildFile; }; 65ED4061235DEF6C0081F399 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849C64671ED37A5D003D8FC0 /* Assets.xcassets */; }; 65ED4062235DEF6C0081F399 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 848362FC2262A30800DA1D35 /* styleSheet.css */; }; 65ED4063235DEF6C0081F399 /* RenameSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363092262A3F000DA1D35 /* RenameSheet.xib */; }; @@ -593,7 +593,6 @@ 84F9EAEB213660A100CF2DE4 /* testIterativeCreateAndDeleteFeed.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAD8213660A100CF2DE4 /* testIterativeCreateAndDeleteFeed.applescript */; }; 84F9EAEC213660A100CF2DE4 /* selectAFeed.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAD9213660A100CF2DE4 /* selectAFeed.applescript */; }; 84F9EAED213660A100CF2DE4 /* uiScriptingTestSetup.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADA213660A100CF2DE4 /* uiScriptingTestSetup.applescript */; }; - 84F9EAEE213660A100CF2DE4 /* testURLsOfCurrentArticle.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADB213660A100CF2DE4 /* testURLsOfCurrentArticle.applescript */; }; 84F9EAEF213660A100CF2DE4 /* testNameOfEveryFolder.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADC213660A100CF2DE4 /* testNameOfEveryFolder.applescript */; }; 84F9EAF0213660A100CF2DE4 /* testFeedOPML.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADD213660A100CF2DE4 /* testFeedOPML.applescript */; }; 84F9EAF1213660A100CF2DE4 /* selectAnArticle.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADE213660A100CF2DE4 /* selectAnArticle.applescript */; }; @@ -601,7 +600,6 @@ 84F9EAF3213660A100CF2DE4 /* testCurrentArticleIsNil.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE0213660A100CF2DE4 /* testCurrentArticleIsNil.applescript */; }; 84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; }; 84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; }; - 84F9EAF7213660A100CF2DE4 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 84F9EAE4213660A100CF2DE4 /* Info.plist */; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; 9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; }; 9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; }; @@ -3339,7 +3337,7 @@ 65ED405D235DEF6C0081F399 /* SidebarKeyboardShortcuts.plist in Resources */, 65ED405E235DEF6C0081F399 /* DefaultFeeds.opml in Resources */, 65ED405F235DEF6C0081F399 /* Preferences.storyboard in Resources */, - 65ED4060235DEF6C0081F399 /* ExportOPMLSheet.xib in Resources */, + 65ED4060235DEF6C0081F399 /* (null) in Resources */, 65ED4061235DEF6C0081F399 /* Assets.xcassets in Resources */, 65ED4062235DEF6C0081F399 /* styleSheet.css in Resources */, 65ED4063235DEF6C0081F399 /* RenameSheet.xib in Resources */, @@ -3424,7 +3422,6 @@ 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */, 844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */, 84A3EE5F223B667F00557320 /* DefaultFeeds.opml in Resources */, - 8459D0F92355794C0050076F /* NNW3OpenPanelAccessoryView.xib in Resources */, 849C78902362AAFC009A71E4 /* ExportOPMLSheet.xib in Resources */, 84C9FC8222629E4800D921D6 /* Preferences.storyboard in Resources */, 849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */, @@ -3760,7 +3757,7 @@ 65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */, 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */, 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */, - 65ED3FEB235DEF6C0081F399 /* ExportOPMLWindowController.swift in Sources */, + 65ED3FEB235DEF6C0081F399 /* (null) in Sources */, 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */, 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */, 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */, @@ -4143,7 +4140,6 @@ 84F9EAF1213660A100CF2DE4 /* selectAnArticle.applescript in Sources */, 84F9EAE9213660A100CF2DE4 /* testNameAndUrlOfEveryFeed.applescript in Sources */, 84F9EAF3213660A100CF2DE4 /* testCurrentArticleIsNil.applescript in Sources */, - 84F9EAEE213660A100CF2DE4 /* testURLsOfCurrentArticle.applescript in Sources */, 84F9EAE7213660A100CF2DE4 /* testNameOfAuthors.applescript in Sources */, 84F9EAEF213660A100CF2DE4 /* testNameOfEveryFolder.applescript in Sources */, 84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */, From cf8d1ee45787dee0fef00389b99a0a5547f0c8c7 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 28 Oct 2019 13:56:46 -0500 Subject: [PATCH 043/237] Fix faded vibrant text. Issue #1186 --- iOS/Account/Account.storyboard | 18 ++++++++++++++---- iOS/Inspector/Inspector.storyboard | 13 +++++++++---- iOS/UIKit Extensions/VibrantButton.swift | 3 ++- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/iOS/Account/Account.storyboard b/iOS/Account/Account.storyboard index b5f05cc65..8abb1d74c 100644 --- a/iOS/Account/Account.storyboard +++ b/iOS/Account/Account.storyboard @@ -3,6 +3,7 @@ + @@ -80,13 +81,15 @@ - - - - - - - - - - - - - - - - - -
diff --git a/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift deleted file mode 100644 index 586d44421..000000000 --- a/Mac/Preferences/Accounts/AccountsFeedlyWebWindowController.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// AccountsFeedlyWebWindowController.swift -// NetNewsWire -// -// Created by Kiel Gillard on 30/8/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import Cocoa -import Account -import WebKit - -class AccountsFeedlyWebWindowController: NSWindowController, WKNavigationDelegate { - - @IBOutlet private weak var webView: WKWebView! - - private weak var hostWindow: NSWindow? - - convenience init() { - self.init(windowNibName: NSNib.Name("AccountsFeedlyWeb")) - } - - // MARK: API - - func runSheetOnWindow(_ hostWindow: NSWindow, completionHandler handler: ((NSApplication.ModalResponse) -> Void)? = nil) { - self.hostWindow = hostWindow - hostWindow.beginSheet(window!, completionHandler: handler) - beginAuthorization() - } - - // MARK: Requesting an Access Token - let client = OAuthAuthorizationClient.feedlySandboxClient - - private func beginAuthorization() { - let request = Account.oauthAuthorizationCodeGrantRequest(for: .feedly, client: client) - webView.load(request) - } - - private func requestAccessToken(for response: OAuthAuthorizationResponse) { - Account.requestOAuthAccessToken(with: response, client: client, accountType: .feedly) { [weak self] result in - switch result { - case .success(let tokenResponse): - self?.saveAccount(for: tokenResponse) - case .failure(let error): - NSApplication.shared.presentError(error) - } - } - } - - // MARK: Actions - - @IBAction func cancel(_ sender: Any) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) - } - - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - - do { - guard let url = navigationAction.request.url else { return } - - let response = try OAuthAuthorizationResponse(url: url, client: client) - - requestAccessToken(for: response) - - // No point the web view trying to load this. - return decisionHandler(.cancel) - - } catch let error as OAuthAuthorizationErrorResponse { - NSApplication.shared.presentError(error) - - } catch { - print(error) - } - - decisionHandler(.allow) - } - - func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - print(error) - } - - private func saveAccount(for grant: OAuthAuthorizationGrant) { - // TODO: Find an already existing account for this username? - let account = AccountManager.shared.createAccount(type: .feedly) - do { - - // Store the refresh token first because it sends this token to the account delegate. - if let token = grant.refreshToken { - try account.storeCredentials(token) - } - - // Now store the access token because we want the account delegate to use it. - try account.storeCredentials(grant.accessToken) - - account.oauthAuthorizationClient = client - - self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) - } catch { - NSApplication.shared.presentError(error) - } - } -} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 11390b61c..5732cbf80 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -276,7 +276,6 @@ 65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */; }; 65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; 65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97621ED9EB96007D329B /* SidebarViewController.swift */; }; - 65ED3FCC235DEF6C0081F399 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; }; 65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */; }; 65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; }; 65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9822153B6B008CE1BF /* TimelineContainerView.swift */; }; @@ -307,7 +306,6 @@ 65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */; }; - 65ED3FEB235DEF6C0081F399 /* (null) in Sources */ = {isa = PBXBuildFile; }; 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; @@ -407,7 +405,6 @@ 65ED4050235DEF6C0081F399 /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; 65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; }; 65ED4052235DEF6C0081F399 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; - 65ED4053235DEF6C0081F399 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; }; 65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 848363062262A3DD00DA1D35 /* Main.storyboard */; }; 65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51EF0F8D2279C9260050506E /* AccountsAdd.xib */; }; 65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8A22629E8F00D921D6 /* NetNewsWire.sdef */; }; @@ -615,14 +612,6 @@ 84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; }; 84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; - 9E7EFDA7237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */; }; - 9E7EFDA8237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */; }; - 9E7EFDC22375072300E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */; }; - 9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; }; - 9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; }; - 9EBD5B062374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */; }; - 9EBD5B072374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */; }; - 9EBD5B082374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */; }; B528F81E23333C7E00E735DD /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = B528F81D23333C7E00E735DD /* page.html */; }; D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; }; D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; }; @@ -1544,10 +1533,6 @@ 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = establishMainWindowStartingState.applescript; sourceTree = ""; }; 84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; }; - 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OAuthAuthorizationClient+NetNewsWire.swift"; sourceTree = ""; }; - 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedlyWebWindowController.swift; sourceTree = ""; }; - 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedlyWeb.xib; sourceTree = ""; }; - 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAccountAuthorizationOperation.swift; sourceTree = ""; }; B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; }; B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; }; B528F81D23333C7E00E735DD /* page.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; @@ -2449,8 +2434,6 @@ 84C9FC6822629C9A00D921D6 /* Shared */ = { isa = PBXGroup; children = ( - 9E7EFDA6237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift */, - 9EBD5B052374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift */, 846E77301F6EF5D600A165E2 /* Account.xcodeproj */, 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */, 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */, @@ -2520,8 +2503,6 @@ 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */, 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */, 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */, - 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */, - 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */, 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */, 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */, 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */, @@ -2808,9 +2789,8 @@ buildConfigurationList = 65ED407F235DEF6C0081F399 /* Build configuration list for PBXNativeTarget "NetNewsWire MAS" */; buildPhases = ( 65ED3FB5235DEF6C0081F399 /* Run Script: Update ArticleExtractorConfig.swift */, - 9E7EFDC3237509DC00E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */, 65ED3FB6235DEF6C0081F399 /* Sources */, - 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */, + 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift */, 65ED4042235DEF6C0081F399 /* Frameworks */, 65ED404D235DEF6C0081F399 /* Resources */, 65ED406F235DEF6C0081F399 /* Run Script: Automated build numbers */, @@ -2859,9 +2839,8 @@ buildConfigurationList = 840D61A32029031E009BC708 /* Build configuration list for PBXNativeTarget "NetNewsWire-iOS" */; buildPhases = ( 517D2D80233A46ED00FF3E35 /* Run Script: Update ArticleExtractorConfig.swift */, - 9E7EFDC4237509F300E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */, 840D61782029031C009BC708 /* Sources */, - 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */, + 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */, 840D61792029031C009BC708 /* Frameworks */, 840D617A2029031C009BC708 /* Resources */, 51C451DF2264C7F200C03939 /* Embed Frameworks */, @@ -2883,9 +2862,8 @@ buildConfigurationList = 849C647A1ED37A5D003D8FC0 /* Build configuration list for PBXNativeTarget "NetNewsWire" */; buildPhases = ( 51D6803823330CFF0097A009 /* Run Script: Update ArticleExtractorConfig.swift */, - 9EBD5B092374CE3E008F3B58 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */, 849C645C1ED37A5D003D8FC0 /* Sources */, - 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */, + 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */, 849C645D1ED37A5D003D8FC0 /* Frameworks */, 849C645E1ED37A5D003D8FC0 /* Resources */, 84C987A52000AC9E0066B150 /* Run Script: Automated build numbers */, @@ -3372,7 +3350,6 @@ 65ED4050235DEF6C0081F399 /* DetailKeyboardShortcuts.plist in Resources */, 65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */, 65ED4052235DEF6C0081F399 /* template.html in Resources */, - 65ED4053235DEF6C0081F399 /* AccountsFeedlyWeb.xib in Resources */, 65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */, 65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */, 65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */, @@ -3458,7 +3435,6 @@ 5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */, 845479881FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist in Resources */, 848362FF2262A30E00DA1D35 /* template.html in Resources */, - 9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */, 848363082262A3DD00DA1D35 /* Main.storyboard in Resources */, 51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */, 84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */, @@ -3536,7 +3512,7 @@ shellPath = /bin/sh; shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n"; }; - 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */ = { + 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3545,16 +3521,16 @@ ); inputPaths = ( ); - name = "Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift"; + name = "Run Script: Reset ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\ngit checkout \"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n"; + shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; }; - 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */ = { + 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3563,14 +3539,14 @@ ); inputPaths = ( ); - name = "Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift"; + name = "Run Script: Reset ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\ngit checkout \"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n"; + shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; }; 51D6803823330CFF0097A009 /* Run Script: Update ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; @@ -3608,7 +3584,7 @@ shellPath = /bin/sh; shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n\n"; }; - 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift */ = { + 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3617,14 +3593,14 @@ ); inputPaths = ( ); - name = "Run Script: Reset ArticleExtractorConfig.swift and OAuthAuthorizationClient+NetNewsWire.swift"; + name = "Run Script: Reset ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\ngit checkout \"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n"; + shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; }; 65ED406F235DEF6C0081F399 /* Run Script: Automated build numbers */ = { isa = PBXShellScriptBuildPhase; @@ -3690,60 +3666,6 @@ shellPath = /bin/sh; shellScript = "# See https://blog.curtisherbert.com/automated-build-numbers/\n\n# WARNING: If automated build numbers are restored then take \n# care to ensure any app extensions are versioned the same as the app.\n# ask Daniel (jalkut@red-sweater.com) if in doubt about this. \n#git=`sh /etc/profile; which git`\n#branch_name=`$git symbolic-ref HEAD | sed -e 's,.*/\\\\(.*\\\\),\\\\1,'`\n#git_count=`$git rev-list $branch_name |wc -l | sed 's/^ *//;s/ *$//'`\n#simple_branch_name=`$git rev-parse --abbrev-ref HEAD`\n\n#build_number=\"$git_count\"\n#if [ $CONFIGURATION != \"Release\" ]; then\n#build_number+=\"-$simple_branch_name\"\n#fi\n\n#plist=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n#dsym_plist=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n#/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$plist\"\n#if [ -f \"$DSYM_INFO_PLIST\" ] ; then\n#/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$dsym_plist\"\n#fi\n"; }; - 9E7EFDC3237509DC00E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n"; - }; - 9E7EFDC4237509F300E94DF8 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n"; - }; - 9EBD5B092374CE3E008F3B58 /* Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Script: Update OAuthAuthorizationClient+NetNewsWire.swift"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Shared/OAuthAuthorizationClient+NetNewsWire.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n"; - }; D519E77022EE5B4100923F27 /* Run Script: Verify No Build Settings */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3829,7 +3751,6 @@ 65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */, 65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */, 65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */, - 65ED3FCC235DEF6C0081F399 /* AccountsFeedlyWebWindowController.swift in Sources */, 65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */, 65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */, 65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */, @@ -3860,7 +3781,6 @@ 65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */, 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */, 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */, - 65ED3FEB235DEF6C0081F399 /* (null) in Sources */, 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */, 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */, 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */, @@ -3896,7 +3816,6 @@ 65ED400C235DEF6C0081F399 /* FolderInspectorViewController.swift in Sources */, 65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */, 65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */, - 9E7EFDA8237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */, 65ED400F235DEF6C0081F399 /* ArticleExtractorButton.swift in Sources */, 65ED4010235DEF6C0081F399 /* AccountsAddTableCellView.swift in Sources */, 65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */, @@ -3927,7 +3846,6 @@ 65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */, 65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */, 65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */, - 9EBD5B072374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */, 65ED402D235DEF6C0081F399 /* DetailStatusBarView.swift in Sources */, 65ED402E235DEF6C0081F399 /* MainWindowController+Scriptability.swift in Sources */, 65ED402F235DEF6C0081F399 /* PreferencesWindowController.swift in Sources */, @@ -3997,7 +3915,6 @@ 51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */, FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */, 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */, - 9E7EFDC22375072300E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */, 51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */, @@ -4032,7 +3949,6 @@ 51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */, 51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */, 51C452AE2265104D00C03939 /* ArticleStringFormatter.swift in Sources */, - 9EBD5B082374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */, 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */, 51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */, 51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */, @@ -4124,7 +4040,6 @@ 849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */, 84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */, 849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */, - 9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */, 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */, 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */, 8405DD9922153B6B008CE1BF /* TimelineContainerView.swift in Sources */, @@ -4135,13 +4050,11 @@ D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */, 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */, 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */, - 9E7EFDA7237502C000E94DF8 /* OAuthAuthorizationClient+NetNewsWire.swift in Sources */, D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */, 8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */, - 9EBD5B062374BD68008F3B58 /* OAuthAccountAuthorizationOperation.swift in Sources */, 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 51FE10092346739D0056195D /* ActivityType.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, From 8a69657b54b516f3fbcd85dff061ee1457530428 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 9 Nov 2019 17:37:25 -0600 Subject: [PATCH 167/237] Convert all image locations to be absolute. Issues #954 & 1280 --- Shared/Article Rendering/main.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Shared/Article Rendering/main.js b/Shared/Article Rendering/main.js index ea399c175..6261daa6f 100644 --- a/Shared/Article Rendering/main.js +++ b/Shared/Article Rendering/main.js @@ -14,6 +14,13 @@ function stripStyles() { document.getElementsByTagName("body")[0].querySelectorAll("[style]").forEach(element => element.removeAttribute("style")); } +// Convert all image locations to be absolute +function convertImgSrc() { + document.querySelectorAll("img").forEach(element => { + element.src = new URL(element.src, document.baseURI).href; + }); +} + function error() { document.body.innerHTML = "error"; } @@ -26,5 +33,7 @@ function render(data) { wrapFrames() stripStyles() + convertImgSrc() + postRenderProcessing() } From a4bcbf5c367ed9dd2a688688c25c63b482eea927 Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Mon, 11 Nov 2019 08:10:39 +1100 Subject: [PATCH 168/237] Automatically refreshes a new Feedly account after its creation. --- .../Feedly/OAuthAccountAuthorizationOperation.swift | 3 +++ .../Accounts/AccountsAddViewController.swift | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift b/Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift index 72e867ef5..bc73c6f3e 100644 --- a/Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift +++ b/Frameworks/Account/Feedly/OAuthAccountAuthorizationOperation.swift @@ -10,6 +10,7 @@ import Foundation import AuthenticationServices public protocol OAuthAccountAuthorizationOperationDelegate: class { + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) } @@ -126,6 +127,8 @@ public final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthentic // Now store the access token because we want the account delegate to use it. try account.storeCredentials(grant.accessToken) + + delegate?.oauthAccountAuthorizationOperation(self, didCreate: account) didFinish() } catch { diff --git a/Mac/Preferences/Accounts/AccountsAddViewController.swift b/Mac/Preferences/Accounts/AccountsAddViewController.swift index 5574248ea..db0def90c 100644 --- a/Mac/Preferences/Accounts/AccountsAddViewController.swift +++ b/Mac/Preferences/Accounts/AccountsAddViewController.swift @@ -119,6 +119,17 @@ extension AccountsAddViewController: NSTableViewDelegate { extension AccountsAddViewController: OAuthAccountAuthorizationOperationDelegate { + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { + account.refreshAll { [weak self] result in + switch result { + case .success: + break + case .failure(let error): + self?.presentError(error) + } + } + } + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { view.window?.presentError(error) } From 4976537a4018e46f57552159c9f83861cec81986 Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Mon, 11 Nov 2019 08:12:54 +1100 Subject: [PATCH 169/237] Enables the iOS app to add Feedly accounts. --- iOS/Settings/AddAccountViewController.swift | 30 ++++++++++++++++++ iOS/Settings/Settings.storyboard | 34 +++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/iOS/Settings/AddAccountViewController.swift b/iOS/Settings/AddAccountViewController.swift index f53d46a55..0f803b8a6 100644 --- a/iOS/Settings/AddAccountViewController.swift +++ b/iOS/Settings/AddAccountViewController.swift @@ -38,6 +38,11 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate let addViewController = navController.topViewController as! FeedbinAccountViewController addViewController.delegate = self present(navController, animated: true) + case 2: + let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly) + addAccount.delegate = self + addAccount.presentationAnchor = self.view.window! + OperationQueue.main.addOperation(addAccount) default: break } @@ -48,3 +53,28 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate } } + +extension AddAccountViewController: OAuthAccountAuthorizationOperationDelegate { + + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { + let rootViewController = view.window?.rootViewController + + account.refreshAll { result in + switch result { + case .success: + break + case .failure(let error): + guard let viewController = rootViewController else { + return + } + viewController.presentError(error) + } + } + + dismiss() + } + + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { + presentError(error) + } +} diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index ff43a99bd..ed9e460df 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -424,6 +424,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -811,6 +844,7 @@ + From 63a42042db569eb3be6ad3fdfaf186f3c9590ca6 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 10 Nov 2019 18:04:21 -0600 Subject: [PATCH 170/237] Add feedly assets --- iOS/AppAssets.swift | 6 ++++++ .../accountFeedly.imageset/Contents.json | 15 +++++++++++++++ .../accountFeedly.imageset/accountFeedly.pdf | Bin 0 -> 4406 bytes 3 files changed, 21 insertions(+) create mode 100644 iOS/Resources/Assets.xcassets/accountFeedly.imageset/Contents.json create mode 100644 iOS/Resources/Assets.xcassets/accountFeedly.imageset/accountFeedly.pdf diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 36e6c057a..20993af6f 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -23,6 +23,10 @@ struct AppAssets { return UIImage(named: "accountFeedbin")! }() + static var accountFeedlyImage: UIImage = { + return UIImage(named: "accountFeedly")! + }() + static var accountFreshRSSImage: UIImage = { return UIImage(named: "accountFreshRSS")! }() @@ -200,6 +204,8 @@ struct AppAssets { } case .feedbin: return AppAssets.accountFeedbinImage + case .feedly: + return AppAssets.accountFeedlyImage case .freshRSS: return AppAssets.accountFreshRSSImage default: diff --git a/iOS/Resources/Assets.xcassets/accountFeedly.imageset/Contents.json b/iOS/Resources/Assets.xcassets/accountFeedly.imageset/Contents.json new file mode 100644 index 000000000..754629a4a --- /dev/null +++ b/iOS/Resources/Assets.xcassets/accountFeedly.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "accountFeedly.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/iOS/Resources/Assets.xcassets/accountFeedly.imageset/accountFeedly.pdf b/iOS/Resources/Assets.xcassets/accountFeedly.imageset/accountFeedly.pdf new file mode 100644 index 0000000000000000000000000000000000000000..907e486bc4a0e1efc925ff12110144ddc8895b53 GIT binary patch literal 4406 zcmai&2UJs8x5p__ARwSpq!o3qb3=9T1qOyFcCPIw{8B*{7U}U z=lyLRU<3dKTrBOuH*Nr;8aQVgcUwT57}5bmRqPzyad_g>5#x?i!eL#kaDa>q*v%b} z!#IJxNj3H4AF{(J>vjeAnl50Yn-}XWP(Dp+11j%g7}id58P;FbiZi3C1QfM5W2crb zqh~Kv@iQJ|X>sNnv}T|pKXN`>%ryB!QB6t(v4*&|JF3xLT|(}avSab2t4 zg$xH*RmwQGZQ@db-P^zQnbq98O5?f;}| z=8`~@OTC70wq2~bVxVl%3R}8wK>KQRTswXGptAX@ZfAId=)v<*G)2hu%9kPU0+%(B zU#WE##aiXi=vsY?=Fbm@+3Z4dfBVMaRlNOKnWL~*|J&PhCE%Q~7*&pbPG5PmoRReG zvuZ9N^D;_?rGonnw9>gZSuD2t@*vV2@OK5@g7}#QIb)@y+OcYHW6$=fdbMXnBxP*r-e}9*hI^wt+kh&fVP<9wC+fV>IxIRcbRk-ls7Bkb}4+WA&4~do!SQ5 zHk6XC^l9f3i{cZ!?1jfut23lLV^`=e?G`(8i3Yz~9N#d`m|)y?Cuj6ac_Xw;uJ$Bs zf~t;l5O)(tdw zS2vb@ifv;p)c=@mp%pp`t!Kf%XA2NSp?&pt$v+j;^_4FpPu>Crz8}1 zI(a%2`8L9FcO$1i_R`axU>IcD=V^m-0wK|%$m6YcvqaCO=O2ofT zr5$&orzaL)k4@NY_>I+&XdMNETHFmu0}+{q4C^b+CKoRDwC$2zq9>&_ByF0pwg##0 zDUl5y)k~+Q7N~Vp?5v9fTGhT6YSea{5DSBeF=bL^6?4zFl{xO@F9_ClrzAt>GFj1mEOm%y(Y0DrX}mMp(V$F|6yA?E z+4m`M^?K@QB=fucLqmH{HAtWgsph?lkW9x2F=Ymp^@uNIB$SWZkac+4sC}gEGZODS zo4H6(gt}$k`LPtc`OjU}2U``o`<5`lN3EJOzjWpxl@_b%?-YuoxUK-kDg4OXZ{mE<-((aG$R-vaEP0U-NpYLdYenb@#VOtw| zBgimL#0ne|FftQxNrI1p#XsGF!Pdi^#4i-7Rjp3ZfI5hMa76S6qR z9td|Op8`li0@dirACrryk=e(lchs7sWB<(4{Jj(z6*+`Y6h4NimSOD@Mf8C&=yhLkcck zeNdFCs3pC9#htZ=()7W>bBcZdarT(hFXVg**%0R3ILWKPiSq00l&UQ~ACkRrZ4TR; z7`6DjbL3~kGchTbm^xTy!_m<*mim}c%bU=V6l{vV2J?c1H9b6NKRDNN?h^X+1Xs+48Qt zI>oRNOxNtTX?dTk0UW>{z&RazxucOUj>-t+5KPDQ7~pZ_(W7UjT%Ip2cN4k|#o zbBEFUw3%26ynHw=U#HFvxt++M9>&A0!y}Md8KkFf&ut2khSYNXrO%`iZJ0!jr!y z&vut2zKC~7T1mdgi+VJc?3HXTlEG)r6>#%=-cdD~cC%cwZcui)ex}MyWs7vEx^h}p zlI3rfX_gU|4?c;vYxCA61SPz_fGU(*Dj(#{NbFHvS^c2Xyh zw13t4>is0{BjJ1!#(Bk4s>MW|t@J;WYkKVg43BJv`JkWcV>Mo2eDoXj1C zFPGOu_*Ng-e`{TqUQT39V{Kq%VZFnejOY+&Npnu?PMc3#M08f#8c|n)jOL9NtIVKD zsq4LZ*1Q!_$jp+`wS3_LnViWN(k0k}3PYRh+ZBBmO%p_nCFNh6yw>$nK;05G6g4l? z-pG{E&M3*qX;f`A4tU$t54^L8l7G@%kQtfTh_8s<&rVc|>ofx9!+?D%*QE0Jh(Z`aJ>JMWWbjaCkNHOFEWJ=>hx zCdZ{Gj;jv^>2A@*t44J`%1>NmB~Y9FmRx2KZ&3C+Vb^lQej{JznoNXDjLgLb&xRL% zqI;NQvSZJK$**IZiw9$eD3BDWnCde)2c!bhr;wo*qq=a$1Jv3~+FToaS#;2x$7Gm^ zOyNk;GxP?PIE^Xef!ie!Aw)4`)u;N7x4*X+NZ3+YNwKX~Ew*d0OEoHn=@yfhO1Eme z@`$RY@;&8bx5^KW`6BauJ7h|?e_i#t?pyZI> zLk5(R+e{;{>ZR(8>a%1h`Kpf|zngyvnk#%1_b6KcUN}(rhh0eJUgeMiFD9~F&=fAs zS1;O%$c8G`ytNxyzjHB5ZXjNR-wkbDoAU6x#y7QSo+rEy(9{+7zfaky;xgW9_nu$a zo=cl^?&0YE+I{FRZY}QRVbkFGJ?yAieBFVzBy_aY8cP;4bVy$8;#R`6}zgcFlI+DMfvmC@4o3>EDeDC#I`n%5eV~x{J0YjuQ~;e7jo-U=icRwXEZk^d?NjdmiUe{c3$-xMH*#} zei)5-l_OzyPvhS7Qs7WJM<0iYrQ&TxNm|j|5I@J@DTxPcQ24DET{_yBd@(Y_%r0I3Pbiewwu;Z9f&e~^>%~$>H zmZ~X%$AjvvpKJZw1Qw|HQBC_1%M<2V2CB%O^s>g>U~NW;Aovk%fsepAN-6oqtV zvc0}i$F|4V)!8gQFzV{FBH5DTk-ahN!usvi=VKOxir;Kv!>ScUPm=b_P_1VOg#-OSa!E)4eV!ZgvfIeZkDPkZAiEQEoOy`d?u=hrMeU)IGGb)C?o%Co@rF2AFHG--d_DX( zKc7T`lsX+qW@&#zZlyM5Y@z4*%h` zfd60e*E@RSan@ksM!>+(|6G8CxHw!Kum*m`;8GIAHs05`;-I4Lm<6brRNAYhVGIGiLB0fSn@ l5#m^^7}5$V1OD%lp9jdzomk5s8;yX9A;G-7$|x1^{{T!xrPlxe literal 0 HcmV?d00001 From c87f8c974a2fe9a41bed151aa9c30c488c6e0541 Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Mon, 11 Nov 2019 17:42:14 +1100 Subject: [PATCH 171/237] Store the last article fetch when articles were successfully fetched. --- Frameworks/Account/Feedly/FeedlyAccountDelegate.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index 2c35ffec3..62742a4fe 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -131,7 +131,9 @@ final class FeedlyAccountDelegate: AccountDelegate { let date = Date() operation.syncCompletionHandler = { [weak self] result in - self?.accountMetadata?.lastArticleFetch = date + if case .success = result { + self?.accountMetadata?.lastArticleFetch = date + } os_log(.debug, log: log, "Sync took %{public}.3f seconds", -date.timeIntervalSinceNow) progress.completeTask() From 91f7da615c3692a4f19d8c89afb1fb183be6f7e3 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 10:13:51 -0600 Subject: [PATCH 172/237] Delete dead code previously used by SwiftUI --- NetNewsWire.xcodeproj/project.pbxproj | 16 -------------- iOS/Model Extensions/Account-Extensions.swift | 22 ------------------- .../RefreshInterval-Extensions.swift | 15 ------------- 3 files changed, 53 deletions(-) delete mode 100644 iOS/Model Extensions/Account-Extensions.swift delete mode 100644 iOS/Model Extensions/RefreshInterval-Extensions.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 5732cbf80..471435d6b 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -118,7 +118,6 @@ 51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; - 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; 51A16997235E10D700EB091F /* RefreshIntervalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */; }; 51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */; }; @@ -128,7 +127,6 @@ 51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16993235E10D600EB091F /* SettingsViewController.swift */; }; 51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16995235E10D600EB091F /* AboutViewController.swift */; }; 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */; }; - 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AF460D232488C6001742EF /* Account-Extensions.swift */; }; 51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; }; 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; }; 51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; }; @@ -1279,7 +1277,6 @@ 51934CCD2310792F006127BE /* ActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityManager.swift; sourceTree = ""; }; 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; - 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RefreshInterval-Extensions.swift"; sourceTree = ""; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.swift; sourceTree = ""; }; 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalAccountViewController.swift; sourceTree = ""; }; @@ -1289,7 +1286,6 @@ 51A16993235E10D600EB091F /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = ""; }; - 51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = ""; }; 51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; 51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; @@ -1831,15 +1827,6 @@ path = Activity; sourceTree = ""; }; - 519D740423243C68008BB345 /* Model Extensions */ = { - isa = PBXGroup; - children = ( - 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */, - 51AF460D232488C6001742EF /* Account-Extensions.swift */, - ); - path = "Model Extensions"; - sourceTree = ""; - }; 51C45245226506C800C03939 /* UIKit Extensions */ = { isa = PBXGroup; children = ( @@ -2557,7 +2544,6 @@ 5123DB95233EC69300282CC9 /* Inspector */, 513145F9235A55A700387FDC /* Intents */, 5183CCEB227117C70010922C /* Settings */, - 519D740423243C68008BB345 /* Model Extensions */, 51C45245226506C800C03939 /* UIKit Extensions */, 513C5CE7232571C2003D4054 /* ShareExtension */, 51314643235A7C2300387FDC /* IntentsExtension */, @@ -3900,7 +3886,6 @@ 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, 517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, - 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */, 51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */, 513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */, 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */, @@ -3988,7 +3973,6 @@ 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */, 516AE9E02372269A007DEEAA /* IconImage.swift in Sources */, - 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */, 51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */, 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */, 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */, diff --git a/iOS/Model Extensions/Account-Extensions.swift b/iOS/Model Extensions/Account-Extensions.swift deleted file mode 100644 index bdad16270..000000000 --- a/iOS/Model Extensions/Account-Extensions.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Account-Extensions.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 9/7/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import Foundation -import Account - -extension AccountType: Identifiable { - public var id: Int { - return rawValue - } -} - -extension Account: Identifiable { - public var id: String { - return accountID - } -} diff --git a/iOS/Model Extensions/RefreshInterval-Extensions.swift b/iOS/Model Extensions/RefreshInterval-Extensions.swift deleted file mode 100644 index 5906d10a2..000000000 --- a/iOS/Model Extensions/RefreshInterval-Extensions.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// RefreshInterval-Extensions.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 9/7/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import Foundation - -extension RefreshInterval: Identifiable { - var id: Int { - return rawValue - } -} From 866988edcf4813e4463ef8503a7ca67c0ca95c15 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 13:47:28 -0600 Subject: [PATCH 173/237] Correct deep link comments. --- Frameworks/Account/Feed.swift | 3 ++- Frameworks/Account/Folder.swift | 3 ++- Shared/Data/ArticleUtilities.swift | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/Feed.swift index 0ca76adcd..cd3a17ec1 100644 --- a/Frameworks/Account/Feed.swift +++ b/Frameworks/Account/Feed.swift @@ -179,7 +179,8 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, De account.renameFeed(self, to: newName, completion: completion) } - // MARK: - PathIDUserInfoProvider + // MARK: - DeepLinkProvider + public var deepLinkUserInfo: [AnyHashable : Any] { return [ DeepLinkKey.accountID.rawValue: account?.accountID ?? "", diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index c341e110a..e23e7804a 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -33,7 +33,8 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun return name ?? Folder.untitledName } - // MARK: - PathIDUserInfoProvider + // MARK: - DeepLinkProvider + public var deepLinkUserInfo: [AnyHashable : Any] { return [ DeepLinkKey.accountID.rawValue: account?.accountID ?? "", diff --git a/Shared/Data/ArticleUtilities.swift b/Shared/Data/ArticleUtilities.swift index e954f215b..51c598115 100644 --- a/Shared/Data/ArticleUtilities.swift +++ b/Shared/Data/ArticleUtilities.swift @@ -94,7 +94,7 @@ extension Article { } } -// MARK: PathIDUserInfoProvider +// MARK: DeepLinkProvider extension Article: DeepLinkProvider { From 766cd2f86864dd830a985ae8910c3cf1589cfa73 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 16:59:42 -0600 Subject: [PATCH 174/237] Save and restore scroll position when transitioning between three column mode and normal. Issue #1242 --- .../MasterTimelineViewController.swift | 16 ++++++++++++++++ iOS/SceneCoordinator.swift | 8 ++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index bf23f7dd1..16e6d9cc1 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -25,6 +25,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner weak var coordinator: SceneCoordinator! var undoableCommands = [UndoableCommand]() + let scrollPositionQueue = CoalescingQueue(name: "Scroll Position", interval: 0.3, maxInterval: 1.0) private let keyboardManager = KeyboardManager(type: .timeline) override var keyCommands: [UIKeyCommand]? { @@ -73,6 +74,13 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner override func viewWillAppear(_ animated: Bool) { applyChanges(animate: false) + + // Restore the scroll position if we have one stored + if let restoreIndexPath = coordinator.timelineMiddleIndexPath { + tableView.scrollToRow(at: restoreIndexPath, at: .middle, animated: false) + } + + // Hide the search controller if we don't have any rows if dataSource.snapshot().numberOfItems < 1 { navigationItem.searchController?.isActive = false } @@ -288,6 +296,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner coordinator.selectArticle(article, animated: true) } + override func scrollViewDidScroll(_ scrollView: UIScrollView) { + scrollPositionQueue.add(self, #selector(scrollPositionDidChange)) + } + // MARK: Notifications @objc dynamic func unreadCountDidChange(_ notification: Notification) { @@ -366,6 +378,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner titleView?.label.text = coordinator.timelineName } + @objc func scrollPositionDidChange() { + coordinator.timelineMiddleIndexPath = tableView.middleVisibleRow() + } + // MARK: Reloading func queueReloadAvailableCells() { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index b57d60ed3..b9c60d2ef 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -135,6 +135,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { var timelineFetcher: ArticleFetcher? { didSet { + timelineMiddleIndexPath = nil + if timelineFetcher is Feed { showFeedNames = false } else { @@ -153,6 +155,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } } + var timelineMiddleIndexPath: IndexPath? + private(set) var showFeedNames = false private(set) var showIcons = false @@ -529,7 +533,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func selectFeed(_ indexPath: IndexPath?, animated: Bool = false) { - guard indexPath != currentFeedIndexPath else { return } + guard indexPath != currentFeedIndexPath else { return } selectArticle(nil) currentFeedIndexPath = indexPath @@ -1589,7 +1593,7 @@ private extension SceneCoordinator { subSplitViewController!.showDetailViewController(navController, sender: self) masterFeedViewController.restoreSelectionIfNecessary(adjustScroll: true) - masterTimelineViewController!.restoreSelectionIfNecessary(adjustScroll: true) + masterTimelineViewController!.restoreSelectionIfNecessary(adjustScroll: false) // We made sure this was there above when we called configureDoubleSplit return subSplitViewController! From d803d686db66a2683bbc5a33d5760baa8c00756e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 17:10:48 -0600 Subject: [PATCH 175/237] Update to latest RSCore --- submodules/RSCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/RSCore b/submodules/RSCore index 972ff3237..6b9d5ace8 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 972ff3237f819a2250e0bc1ca2814bafe328fa69 +Subproject commit 6b9d5ace8ba71ab4c59663a50c673a6211ac5ed6 From 4c97d099ea7275f34bf236e7700a459cb85eb2a6 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 17:57:49 -0600 Subject: [PATCH 176/237] Add ProjectSettings.xcconfig to be included in the Account build. --- Frameworks/Account/xcconfig/Account_project.xcconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/Frameworks/Account/xcconfig/Account_project.xcconfig b/Frameworks/Account/xcconfig/Account_project.xcconfig index b7f10e36e..8e096dabd 100644 --- a/Frameworks/Account/xcconfig/Account_project.xcconfig +++ b/Frameworks/Account/xcconfig/Account_project.xcconfig @@ -7,6 +7,7 @@ PROVISIONING_PROFILE_SPECIFIER = // DeveloperSettings.xcconfig is #included here #include? "../../../SharedXcodeSettings/DeveloperSettings.xcconfig" +#include? "../../../SharedXcodeSettings/ProjectSettings.xcconfig" SDKROOT = macosx MACOSX_DEPLOYMENT_TARGET = 10.15 From 3b3dd9f1bd04277ad52f10af9afba3e3e445c483 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 18:18:59 -0600 Subject: [PATCH 177/237] Always use cloud environment for Feedly. --- Frameworks/Account/Feedly/FeedlyAccountDelegate.swift | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index 62742a4fe..fbec23b02 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -18,16 +18,9 @@ final class FeedlyAccountDelegate: AccountDelegate { /// Feedly has a sandbox API and a production API. /// This property is referred to when clients need to know which environment it should be pointing to. /// The value of this proptery must match any `OAuthAuthorizationClient` used. + /// Currently this is always returning the cloud API, but we are leaving it stubbed out for now. static var environment: FeedlyAPICaller.API { - #if DEBUG - // https://developer.feedly.com/v3/developer/ - if let token = ProcessInfo.processInfo.environment["FEEDLY_DEV_ACCESS_TOKEN"], !token.isEmpty { - return .cloud - } - return .sandbox - #else return .cloud - #endif } // TODO: Kiel, if you decide not to support OPML import you will have to disallow it in the behaviors From d8b1b6c236eebc69b1a1bf42ae2210624d4120a3 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 11 Nov 2019 20:45:14 -0600 Subject: [PATCH 178/237] Stop adjusting the scroll position on every appearance. --- iOS/MasterTimeline/MasterTimelineViewController.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 16e6d9cc1..d340e7109 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -69,10 +69,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner resetEstimatedRowHeight() resetUI() - - } - - override func viewWillAppear(_ animated: Bool) { + applyChanges(animate: false) // Restore the scroll position if we have one stored @@ -80,6 +77,9 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner tableView.scrollToRow(at: restoreIndexPath, at: .middle, animated: false) } + } + + override func viewWillAppear(_ animated: Bool) { // Hide the search controller if we don't have any rows if dataSource.snapshot().numberOfItems < 1 { navigationItem.searchController?.isActive = false From 701070f2dd5af6fdd09725550cf308a43479bd9d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 12 Nov 2019 09:22:23 -0600 Subject: [PATCH 179/237] Make Article icons/avatars match Timeline icons/avatars. Issue #1273 --- .../Detail/DetailWebViewController.swift | 4 + .../TimelineIconView.swift => IconView.swift} | 6 +- .../Timeline/Cell/TimelineTableCellView.swift | 2 +- NetNewsWire.xcodeproj/project.pbxproj | 22 +-- .../ArticleIconSchemeHandler.swift | 0 .../Article Rendering/ArticleRenderer.swift | 128 +----------------- submodules/RSCore | 2 +- 7 files changed, 25 insertions(+), 139 deletions(-) rename Mac/MainWindow/{Timeline/Cell/TimelineIconView.swift => IconView.swift} (94%) rename {iOS/Article => Shared/Article Rendering}/ArticleIconSchemeHandler.swift (100%) diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 4ebf7a697..82dc20524 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -39,6 +39,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { } #endif + private let articleIconSchemeHandler = ArticleIconSchemeHandler() private var waitingForFirstReload = false private let keyboardDelegate = DetailKeyboardDelegate() @@ -65,6 +66,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { let configuration = WKWebViewConfiguration() configuration.preferences = preferences + configuration.setURLSchemeHandler(articleIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme) let userContentController = WKUserContentController() userContentController.add(self, name: MessageName.mouseDidEnter) @@ -185,8 +187,10 @@ private extension DetailWebViewController { case .loading: rendering = ArticleRenderer.loadingHTML(style: style) case .article(let article): + articleIconSchemeHandler.currentArticle = article rendering = ArticleRenderer.articleHTML(article: article, style: style) case .extracted(let article, let extractedArticle): + articleIconSchemeHandler.currentArticle = article rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style) } diff --git a/Mac/MainWindow/Timeline/Cell/TimelineIconView.swift b/Mac/MainWindow/IconView.swift similarity index 94% rename from Mac/MainWindow/Timeline/Cell/TimelineIconView.swift rename to Mac/MainWindow/IconView.swift index f537c3124..92d9f30dd 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineIconView.swift +++ b/Mac/MainWindow/IconView.swift @@ -8,7 +8,7 @@ import AppKit -final class TimelineIconView: NSView { +final class IconView: NSView { var iconImage: IconImage? = nil { didSet { @@ -71,13 +71,13 @@ final class TimelineIconView: NSView { return } - let color = NSApplication.shared.effectiveAppearance.isDarkMode ? TimelineIconView.darkBackgroundColor : TimelineIconView.lightBackgroundColor + let color = NSApplication.shared.effectiveAppearance.isDarkMode ? IconView.darkBackgroundColor : IconView.lightBackgroundColor color.set() dirtyRect.fill() } } -private extension TimelineIconView { +private extension IconView { func commonInit() { addSubview(imageView) diff --git a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift index 219e7bdc5..2271a7a53 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift @@ -18,7 +18,7 @@ class TimelineTableCellView: NSTableCellView { private let dateView = TimelineTableCellView.singleLineTextField() private let feedNameView = TimelineTableCellView.singleLineTextField() - private lazy var iconView = TimelineIconView() + private lazy var iconView = IconView() private let starView = TimelineTableCellView.imageView(with: AppAssets.timelineStar, scaling: .scaleNone) private let separatorView = TimelineTableCellView.separatorView() diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 471435d6b..1c8df4ab1 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -113,6 +113,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 */; }; 51934CCB230F599B006127BE /* ThemedNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CC1230F5963006127BE /* ThemedNavigationController.swift */; }; 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; 51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; @@ -255,7 +257,7 @@ 6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; }; 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; }; - 65ED3FB9235DEF6C0081F399 /* TimelineIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */; }; + 65ED3FB9235DEF6C0081F399 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; }; 65ED3FBA235DEF6C0081F399 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; }; 65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */; }; 65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; @@ -489,7 +491,7 @@ 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; }; 8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; - 847CD6CA232F4CBF00FAC46D /* TimelineIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */; }; + 847CD6CA232F4CBF00FAC46D /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; }; 847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */; }; 848362FD2262A30800DA1D35 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 848362FC2262A30800DA1D35 /* styleSheet.css */; }; 848362FF2262A30E00DA1D35 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; @@ -1407,7 +1409,7 @@ 8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = ""; }; 847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedDelegate.swift; sourceTree = ""; }; - 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineIconView.swift; sourceTree = ""; }; + 847CD6C9232F4CBF00FAC46D /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = ""; }; 848362FC2262A30800DA1D35 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; 848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = ""; }; @@ -1910,7 +1912,6 @@ children = ( 51C4527E2265092C00C03939 /* ArticleViewController.swift */, 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */, - 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */, 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */, 5142192923522B5500E07E2C /* ImageViewController.swift */, 514219362352510100E07E2C /* ImageScrollView.swift */, @@ -1934,10 +1935,11 @@ 51C452A822650DA100C03939 /* Article Rendering */ = { isa = PBXGroup; children = ( - 49F40DEF2335B71000552BF4 /* newsfoot.js */, + 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */, 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */, - 848362FE2262A30E00DA1D35 /* template.html */, 517630032336215100E15FFF /* main.js */, + 49F40DEF2335B71000552BF4 /* newsfoot.js */, + 848362FE2262A30E00DA1D35 /* template.html */, ); path = "Article Rendering"; sourceTree = ""; @@ -2068,6 +2070,7 @@ 519B8D322143397200FA689C /* SharingServiceDelegate.swift */, 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */, 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */, + 847CD6C9232F4CBF00FAC46D /* IconView.swift */, 844B5B6B1FEA224B00C7C76A /* Keyboard */, 849A975F1ED9EB95007D329B /* Sidebar */, 849A97681ED9EBC8007D329B /* Timeline */, @@ -2245,7 +2248,6 @@ 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */, 849A97711ED9EC04007D329B /* TimelineCellData.swift */, 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */, - 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */, ); path = Cell; sourceTree = ""; @@ -3718,7 +3720,7 @@ files = ( 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */, 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */, - 65ED3FB9235DEF6C0081F399 /* TimelineIconView.swift in Sources */, + 65ED3FB9235DEF6C0081F399 /* IconView.swift in Sources */, 65ED3FBA235DEF6C0081F399 /* ArticleExtractorConfig.swift in Sources */, 65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */, 65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */, @@ -3768,6 +3770,7 @@ 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */, 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */, 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */, + 518C3194237B00DA004D740F /* ArticleIconSchemeHandler.swift in Sources */, 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */, 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */, 65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */, @@ -4003,7 +4006,7 @@ files = ( 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */, 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */, - 847CD6CA232F4CBF00FAC46D /* TimelineIconView.swift in Sources */, + 847CD6CA232F4CBF00FAC46D /* IconView.swift in Sources */, 51FA73AA2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */, 84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */, 51EF0F7A22771B890050506E /* ColorHash.swift in Sources */, @@ -4098,6 +4101,7 @@ 848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */, 5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */, 849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */, + 518C3193237B00D9004D740F /* ArticleIconSchemeHandler.swift in Sources */, 84C9FC6722629B9000D921D6 /* AppDelegate.swift in Sources */, 84C9FC7A22629E1200D921D6 /* AccountsTableViewBackgroundView.swift in Sources */, 84CAFCAF22BC8C35007694F0 /* FetchRequestOperation.swift in Sources */, diff --git a/iOS/Article/ArticleIconSchemeHandler.swift b/Shared/Article Rendering/ArticleIconSchemeHandler.swift similarity index 100% rename from iOS/Article/ArticleIconSchemeHandler.swift rename to Shared/Article Rendering/ArticleIconSchemeHandler.swift diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 93d1676cf..ae74fd84c 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -31,9 +31,8 @@ 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, useImageIcon: Bool = false) { + private init(article: Article?, extractedArticle: ExtractedArticle?, style: ArticleStyle) { self.article = article self.extractedArticle = extractedArticle self.articleStyle = style @@ -45,13 +44,12 @@ 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, useImageIcon: Bool = false) -> Rendering { - let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style, useImageIcon: useImageIcon) + let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style) return (renderer.styleString(), renderer.articleHTML) } @@ -104,9 +102,6 @@ private extension ArticleRenderer { return renderHTML(withBody: "") } - static var faviconImgTagCache = [Feed: String]() - static var feedIconImgTagCache = [Feed: String]() - static var defaultStyleSheet: String = { let path = Bundle.main.path(forResource: "styleSheet", ofType: "css")! let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) @@ -146,13 +141,7 @@ private extension ArticleRenderer { d["title"] = title d["body"] = body - - d["avatars"] = "" - var didAddAvatar = false - if let avatarHTML = avatarImgTag() { - d["avatars"] = "\(avatarHTML)"; - didAddAvatar = true - } + d["avatars"] = ""; var feedLink = "" if let feedTitle = article.feed?.nameForDisplay { @@ -163,12 +152,6 @@ private extension ArticleRenderer { } d["feedlink"] = feedLink - if !didAddAvatar, let feed = article.feed { - if let favicon = faviconImgTag(forFeed: feed) { - d["avatars"] = "\(favicon)"; - } - } - let datePublished = article.logicalDatePublished let longDate = dateString(datePublished, .long, .medium) let mediumDate = dateString(datePublished, .medium, .short) @@ -200,111 +183,6 @@ private extension ArticleRenderer { return permalink != preferredLink // Make date a link if it’s a different link from the title’s link } - func faviconImgTag(forFeed feed: Feed) -> String? { - - if let cachedImgTag = ArticleRenderer.faviconImgTagCache[feed] { - return cachedImgTag - } - - if let iconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) { - if let s = base64String(forImage: iconImage.image) { - var dimension = min(iconImage.image.size.height, CGFloat(ArticleRenderer.avatarDimension)) // Assuming square images. - dimension = max(dimension, 16) // Some favicons say they’re < 16. Force them larger. - if dimension >= CGFloat(ArticleRenderer.avatarDimension) * 0.8 { //Close enough to scale up. - dimension = CGFloat(ArticleRenderer.avatarDimension) - } - - let imgTag: String - if dimension >= CGFloat(ArticleRenderer.avatarDimension) { - // Use rounded corners. - imgTag = "" - } - else { - imgTag = "" - } - ArticleRenderer.faviconImgTagCache[feed] = imgTag - return imgTag - } - } - - return nil - } - - func feedIconImgTag(forFeed feed: Feed) -> String? { - if let cachedImgTag = ArticleRenderer.feedIconImgTagCache[feed] { - return cachedImgTag - } - - if useImageIcon { - return "" - } - - if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) { - if let s = base64String(forImage: iconImage.image) { - #if os(macOS) - let imgTag = "" - #else - let imgTag = "" - #endif - ArticleRenderer.feedIconImgTagCache[feed] = imgTag - return imgTag - } - } - - return nil - } - - func base64String(forImage image: RSImage) -> String? { - return image.dataRepresentation()?.base64EncodedString() - } - - func singleArticleSpecifiedAuthor() -> Author? { - // The author of this article, if just one. - if let authors = article?.authors, authors.count == 1 { - return authors.first! - } - return nil - } - - func singleFeedSpecifiedAuthor() -> Author? { - if let authors = article?.feed?.authors, authors.count == 1 { - return authors.first! - } - return nil - } - - static let avatarDimension = 48 - - struct Avatar { - let imageURL: String - let url: String? - - func html(dimension: Int) -> String { - let imageTag = "" - if let url = url { - return imageTag.htmlByAddingLink(url) - } - return imageTag - } - } - - func avatarImgTag() -> String? { - 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) { - return imgTag - } - if let feedIconURL = article?.feed?.iconURL { - return Avatar(imageURL: feedIconURL, url: article?.feed?.homePageURL ?? article?.feed?.url).html(dimension: ArticleRenderer.avatarDimension) - } - if let author = singleFeedSpecifiedAuthor(), let imageURL = author.avatarURL { - return Avatar(imageURL: imageURL, url: author.url).html(dimension: ArticleRenderer.avatarDimension) - } - return nil - } - func byline() -> String { guard let authors = article?.authors ?? article?.feed?.authors, !authors.isEmpty else { return "" diff --git a/submodules/RSCore b/submodules/RSCore index 6b9d5ace8..ff2072b8d 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 6b9d5ace8ba71ab4c59663a50c673a6211ac5ed6 +Subproject commit ff2072b8da8f3a716524e87165010301e78a72ab From 0d2583948ac54894f8f0626e4ce0b8bededb917d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 12 Nov 2019 13:05:52 -0600 Subject: [PATCH 180/237] Add additional assets for Article Extractor Button for when the app is inactive. --- Mac/AppAssets.swift | 8 ++++++++ Mac/MainWindow/ArticleExtractorButton.swift | 10 +++++++++- .../ArticleExtractorInactiveDark.pdf | Bin 0 -> 3911 bytes .../Contents.json | 12 ++++++++++++ .../ArticleExtractorInactiveLight.pdf | Bin 0 -> 3911 bytes .../Contents.json | 12 ++++++++++++ 6 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf create mode 100644 Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/Contents.json create mode 100644 Mac/Resources/Assets.xcassets/articleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight.pdf create mode 100644 Mac/Resources/Assets.xcassets/articleExtractorInactiveLight.imageset/Contents.json diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index ab5eb8dc6..ddab9827d 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -50,6 +50,14 @@ struct AppAssets { return RSImage(named: "articleExtractorError") }() + static var articleExtractorInactiveDark: RSImage! = { + return RSImage(named: "articleExtractorInactiveDark") + }() + + static var articleExtractorInactiveLight: RSImage! = { + return RSImage(named: "articleExtractorInactiveLight") + }() + static var articleExtractorProgress1: RSImage! = { return RSImage(named: "articleExtractorProgress1") }() diff --git a/Mac/MainWindow/ArticleExtractorButton.swift b/Mac/MainWindow/ArticleExtractorButton.swift index 69e250ed5..67b9f75da 100644 --- a/Mac/MainWindow/ArticleExtractorButton.swift +++ b/Mac/MainWindow/ArticleExtractorButton.swift @@ -59,7 +59,15 @@ class ArticleExtractorButton: NSButton { case isInProgress: addAnimatedSublayer(to: hostedLayer) default: - addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractor, opacity: opacity) + if NSApplication.shared.isActive { + addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractor, opacity: opacity) + } else { + if NSApplication.shared.effectiveAppearance.isDarkMode { + addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractorInactiveDark, opacity: opacity) + } else { + addImageSublayer(to: hostedLayer, image: AppAssets.articleExtractorInactiveLight, opacity: opacity) + } + } } } diff --git a/Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf b/Mac/Resources/Assets.xcassets/articleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf new file mode 100644 index 0000000000000000000000000000000000000000..28edebd24c5931a9792c2a4911fa836f56325880 GIT binary patch literal 3911 zcmai%c{r49`^PO)7_yXzRCgXr3bPqYWgjL{wv@3pV+Lazjj=abvOE~cmMA15WT{b! zhU~;+k}VYyL$;)3P2QpUJw5OH{*L!P?&H3$<9nUgd0f}`y#M%|vSuhlC73b-EZfNX z%$hFva_dE7Ggt+H0wkOp`1o-EVnXn8p|}D_wqyZ742d2T0+~H}U?~I?!HI+?09smL z9}1a(^#liS-bpeR4A&7xE&0S&aj5_G$v&9t-2PBk72RE9-(&0tg~jzuyz3~i%t>@F z^nHYgh`mEj)Q8oocQkpN!|0WU3p*E*Yf=eGJ$Y8hudPJ@a=&m`U)aMQ>}6Hw3k|*2 z@m|S&Q}iZ&?Z9oOyR18ON(AvOsUqBih4hUMU5badB>P=I2h9ac@WTJR8hf_s$fgaOBOI!}xwtj`&8#zl-UI+*hIQHck_lcE0P({(3xW^H zm+VCF0aSko`Xnz3d))`vG}~g+#Gm;#*MFIBMkYB~5-5NzJ1NQ#um>ReBo7kV(i`hU z05+?m?*jwWekS-Whx%_heil$;D++R&eISVLW)Z#E6$BtCf*;X|V1d^A-?h!W=6}X= zKrEWcP-DEM7Qop(;7nq93R0 z>~`*IZ(QQqBg`pi&G~G^*%^dhMsf9gV`!$Q-#yVxU;L;Xim!g7SZgNd)3*_lD6OV1 zvz-HM(5rgh4~?X~F>&O?T{=kjwFifg6ZQ ze2n?b#lf4>sPU967`LL~n#B>2?=lt(iZR0Fi}YV7vOcsrug=pgS8%W+DGjGa{tPf- zE>s9yfr^Kgk**nV%3?gzT@z5Z zdk}^g$D)kQjeYc*R4a}O&+R&_k$a|d0LSNT92G-1zwD3la7b!=FCfn!H#6hm!$ey}qCkir)c<60egJk0v4G9DZe(mN8eM^&Cj;!>u&sH?9d2Q^Z|tkQ_5 zm88j`{|C2>mv#^rb=p=K25zl+>ROUm=92&q`aZFGfZs0GYs5rX+H!nRAOTO7y{#yb zI~?Ir$F=h+59JnEB}^I0rFedaR4BrmdkEl&2t6UpeT`fB1Q$^U^gvhZ6$e-s_KMT* zvYR(YPgu|k(CKhh@9lP=bI*2o^9zKcb-+<|#!$|oFd}dGC*j?>o}z&|E8jmCpv` z9xin78X)5#b4FN_cY=34LZn{BJnXJ1MLJ42u+a{uaR16H-KR!Ua;K9;jiO}4Eo2nZ zE5gngxk=m0Y05DUCbu2MqseZ+fBo`;Tr%tB@%bKS2r}?KjDrd_(9t=78 zJ^x!JmwBB|on=^V`Pm%9k&1fFNF#%c+o`xeaT&N6+~xPkCUe=RNnuHkcbXRJOqRct zy>n1fulr8m%gTyV6-QqaUPJex)$-Q5lP8-N4t!uN@DXGQwyZ-eDV8LQ{@h}FJ@ouC z!b0K>EtytLOZcKP{)9Qoe4|=Mbl7;Rkh*f%>+r=?=~U-b&eVh=6Dv}$Xm4hbXOU?S z)J@I}>o(xF)~j3`&n#g2XE8G!RW`=HMNgGi#ROHZx_xb!(ws_`%#f^+l#sNNOjBt_ zN@RFtv}Ld|CRAD~Ty6Ltf^1ke6AvArsp%g(&N$1`<1}(gN@oj{y0r2Ji#1D}?$WJY za!=DcciAT?+p6h4wtH;puV-ouv4%L7na|~DnP-<|=hdQXZ9|?vdjY&YAE$ex?ru(O zPAxVIn>*t4M^R#N*Wp~7++)q^PLD3c&TEa@%9Y1t8YLOUx1F+l)iZSFd=;~TDd0Zt zDyLG6tZuno{uYC|XV`a3qv3e##oWLfD@XV>)h0YzFx%>j+pTv8V_K>?Sj8saI z6P5AGG{*q%oF1z;siQX2s~xBM<0t$YJsSsKX%4Jct{o9F7D`0NwWJgzPe?NP9sW!! zJC}H_>~Yc(Zq9A4KtQx|I@^3|C%XC9dwWH12_+4 z2s+E7#SiD(xy={UP{&zU9ex1v(ox2)M~qAFo4#Mmr5`-X)#Vs_ORvP?=B z9H02JK>f+p(Q8Q)%||BU=k1pX-x5J-5f37|P-%_!F-}IMM!SthxlDB*zVm(H_&IE> zFy(4Wt^%U4tMD&kM8$GNx1KCEw)}`aLQ|drX;8_9>Q_A{_I|Y5bz7$^(d3X1#<}|X zmG35BPtas;$X>?q)7>5nxu6Nz&&@kT#uvsi#=P41wS8$@yM%N``un=n_$!UN4x~h?{HPZ>6fai&UPwj^7@r1p@Ua>%njXJFqJnI zCYg{i&X@{*Je&EtuxCg&XFPWkkJf%ssiz^WOJ?pRx}HtgwG$jt9a53k^5vBq<&Ci z{(Z~xfw?S!;{$=Q)S^lG_3Xt*FKl9Ma{Au(#XQPWB?g-W4^M`6XYT9Vr=)#j9k+e;qjF8~hQnLUN$Svg7f+#(gLX%d`nv17)6D2?d9dl>tRn4__1@)v=R(H9 z%&q?OO!}WL2~m~$kd4%pGSh}_%tGc7!wSPGbP1YCTcHgG-Ct<>)J{w59*wM3`2J`q zwW~P$lK&dR1=rU2$-QejUHjzF-5QP6zD3Uo{D-k_f8tzzLF`a*jrL;Ldd`~DLQCT0 zjOnszYIeM~|3$uUp9ADPoyUilBEGEsCqHje=oU1q!r;GvcavW?H8$iL>FeuZeF%79 z6JRX>`(GiO5dDjZ|7L6-0CI|eCt~$T0e~%(9RgEjzgv*(%O-Ca05K-wec12}|B>xC zK!$CG|1+aLmV)&lx%|L)pP$_RHaQVQYA2fw4tc z$1RHklx(+h4;fWLj^+w6tYpQ$#krcdo8QyNR?@I*0D5gx*0sGkSDOdnmnt_gy~}t{ z3;{xNCH}k`Yqsskwhh}O9ICv%_fVNPvO?&i?zCN^+$Eh#$rok=!XBR07Ez zQ2ZfiQ(S4Rb$4LfY@=-xf9Bg>|7E@bl|nEk(Ev+UQnW5$4Jc?+oGDaeHynWkY*$Cy z9R?`L{<==ArETGEwD24N^11V^37txhfK|ldb@+1>TMi{OCU)ziuUKfmqgu>}{ zN_8*xNB~kxqQ2a~%af<~fej<{0u9*1LZm&>J9Gti%bF4|JY~a#OAvGfd9DTH2e}so z4B8qE(K^iGXeaLZ64d9B^v!nj(V@wz!6Ey3|G^rN!Pj7L6U~e*5Se8r_;IGuYR}%z zmKFB>0&IL{Y)=_>c3{jZn!WF9ooZTI!RfY&<&W}##M(D<4F=Ng16u*{5=z>VJ3$bg ze#KK)rG=U7NyZheBJeHn^QB~2co+;Wl*OH0EHTzn=DeIgajdo@HAQ+nOVqquo#h+>*AW!?7_rC> z;!0{!c|zrjT2pbj4f4*jACCk_=;8Br52litAD-E*FH{(>;bF&;n$I!*^wy^@Rq|ek z3I~=^Zs@Q{nY*Mp#Ltz8&&`<+OgOAH42>SVYP_lX%y+|NL3);cTQmB$`Ka@3u|k~? zXj#(Y=7Ea(WD`)O%T5g@qZm66fEIUvDo1X8J{aa~lhE>>SB58QcGlUQEz?Q<*axMJ z%wWp%hA@?tw9bzkrYG}+)r0=f+lL_OF!S>B&Z4`zc==N3#LX3ft+A&nlY*K@+Xvvc9%{hw}x1+_}l-AF?}@y%)FZ73fjm zQ29x5E=*JBJGA${Tb#mA-!RTp3#su{H3>7sK7r&yZJQFgG@fyB^yr##i7lYD;>;x3 zb{}?%m&j!=)Jc!y%N2VZb2|R=ILFTLEL`e-p*E4RaPw#e-V`^0KLhPgC8V0_3r{H8 z2_S;jg74z{@8_Ax6svo3EAGA-bG*g$QtTzm@|61=uW+*`#rfKwio6$hyIQ^93hs(3 z1y}J8M%M<;H1A?aFL*yWIwCk9%-`t#34i5qJtROp;NWQNp|%E@IBpBDQ!xMG8-S#< znbMrD&~sGd%Gb~64?d^$b?f9*Z0)llk5LQq209&Dk;RQXG(yN5-!dhhzY ztgbv)dEzzcHS_>RDR-kcajJFc(1*GuZjuzql6izF&J<-6G+ne_R7BK7GzHm)5=nPW??`8+ zPa@kZ9V~brf-RU9lMij6$!QbNa6jYem=bW$T-fNv8 zZ>gkNWmRSDrG-7KV5VSOW;mavW|&!$ncIMAunc(q^ab$xQk3S+#)7QKtOnd|Tn>Zq z$G!NXo})PyIVanc36CyEE~t%JN|#4u=q2dIbeuDO)i-kCQVqS5&g(ScAdM_S)wXAs zzcn{6)*ZN`(tPUKl^ma&YsYw0qs*c*gocG|<>us!kI%>*GM6>qVdhdfUB2G@x+UGo zn&96a<~T;E=*}CScvw<+eAKgH&i`b(b@{MR?fF{zGT*YqhV%xEQ#IT>ypC_>70vTV zZHRyW?wJpa>h6W;F-{pjnftwZ`J)-;-Ll=gx^sAVg+B@R9p6vwxy+~*r^d?@<)dxA z-Lm>j-XxD%%&d2v8;qIsY;kEBex*9JS-o+L|15tzCaOItKXFo&&SUduO4-Hui)B>_ zEBJZG`Fyn#Y7uHNYJ2NF>x=vqR&kr`o1W{#p9eopt`BZt!OGxb?hlY$urAn?Q;i4C zy=R99xVe$7u{QXS!b@98t3Dxit*_dip{KY}yw>~H-S^AOA&aGFeX32ouX=ldkK=XF z+AX!GW1qczhKY(1IxFO*+kt7->BpSY@zY7sIh$f8XI?R`8*LE{}%>nW??72G|sls{PNL*Vuucl3T#E}Z7O8ymoC{p_F{qu z6Klga5+>V@O~x!(uadsTgHu8tg!G_OTC5`odZl{%^v2k+nh)Q3Jg{918ZS(WOUjW& z6!sMUgB()1TG^{5g^MgdW{pslsZ(f1=0LS;o|F4On(WQi=!w@q;%;tNn|l45{+H9y zk~gKUne$XQJ{YmbkTRbecI}>68c!d0?L5%&xntuh$`0k_VPEh0E$nL}>Z221N$5bS z)tkrXy{sE+7+>mVVqE~2(?g#+bvt87Vxz8+BYlQ<49n{;IaJI~v}rxoGGx>p@3!P~ zZgt-0JU47tQR4U}MZ%`%3%>JxbN`EL!(GcEb9Hx@?{0X#^W^e+J3LkM z*(?ojZZq_v^qqAHvuiPXv8wjsY`rPY^718qax5{n5?=72?o62@rayl=cRENkHhrRQ z+OKLZ<8}L+!G=+nfL^v3j#f^!gM#8u0-miHIF)F+wBp_}iks;(+0!)*_5rp5=(*Di z@pMcm=5BjatVjRJzCon|r6k3sn1r_bW0%`0oA178`|T~7GC<@!UIi;3R#|x8zItf> zHt(q+pGg0EQ!<;G%a2}IL|SAGyd8*ml&eVg)At*l3hd1|(0$;z`pvB?OVP_|=@rlI z&(h<&_uaawlIeAIQL8OA+R9IU;4!*=#6dFtW3OWl-P#paE%lI!(5{3q9~Hxx#eYy6qlw=%>y zqIRx-l&|;OvU#gIt>m>tWOfnbl`zNcQY*}pnyS6H_+duQ+x zz2Z;%*syACg{|bZGHmk>dLjLoZl&%trUXNeUW*>~y}#7@sWUpIcPz9)_S>VC?<_3Fz(n^_x#rS|x#S?nq{ zIWtDx>k9YRMQ>>ryNS`2kk1?c$1TVentTyUnlL8Vhpuw6(Qx?j$0x4X{Rl z^{XMA0Wxem z{O=jHaWtGW#r_AryZ_|&zpxzk+Xfbv6Fms5KIn)Gj({}=Oi5ICGQ|~uLFHj6c^F`I zM9YKhOax#mTFS~+vVfrnj!N?dSP1`}`n_qgEOKXcOf=ReGz-HaN6;tWP?!n~27|+t zkT4|50t%I3y{x!%6ykOW@V`U;I-@t0WCvl{0E0mP^#F<}6aod<0Y5baLWyR$Xf)Wu Date: Tue, 12 Nov 2019 15:36:03 -0600 Subject: [PATCH 181/237] Change to use Favicon generator. --- Mac/AppAssets.swift | 6 ------ Mac/Inspector/FeedInspectorViewController.swift | 2 +- Mac/MainWindow/SharingServicePickerDelegate.swift | 2 +- Mac/MainWindow/Sidebar/Cell/SidebarCell.swift | 3 +-- Shared/Data/SmallIconProvider.swift | 4 ---- 5 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index ddab9827d..644ce65ea 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -16,12 +16,6 @@ extension NSImage.Name { struct AppAssets { - static var genericFeedImage: IconImage? = { - let path = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/BookmarkIcon.icns" - let image = RSImage(contentsOfFile: path) - return image != nil ? IconImage(image!) : nil - }() - static var timelineStar: RSImage! = { return RSImage(named: .timelineStar) }() diff --git a/Mac/Inspector/FeedInspectorViewController.swift b/Mac/Inspector/FeedInspectorViewController.swift index 0c2bbd568..18ac3e770 100644 --- a/Mac/Inspector/FeedInspectorViewController.swift +++ b/Mac/Inspector/FeedInspectorViewController.swift @@ -119,7 +119,7 @@ private extension FeedInspectorViewController { return } - imageView?.image = AppAssets.genericFeedImage?.image + imageView?.image = feed.smallIcon?.image } func updateName() { diff --git a/Mac/MainWindow/SharingServicePickerDelegate.swift b/Mac/MainWindow/SharingServicePickerDelegate.swift index 5e2d38dc0..45cbd9eb7 100644 --- a/Mac/MainWindow/SharingServicePickerDelegate.swift +++ b/Mac/MainWindow/SharingServicePickerDelegate.swift @@ -41,7 +41,7 @@ import RSCore return nil } - let image = sendToCommand.image ?? AppAssets.genericFeedImage?.image ?? NSImage() + let image = sendToCommand.image ?? NSImage() return NSSharingService(title: sendToCommand.title, image: image, alternateImage: nil) { sendToCommand.sendObject(object, selectedText: nil) } diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift index 610a51ace..7b021a073 100644 --- a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift +++ b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift @@ -81,8 +81,7 @@ class SidebarCell : NSTableCellView { }() private let faviconImageView: NSImageView = { - let iconImage = AppAssets.genericFeedImage - let imageView = iconImage != nil ? NSImageView(image: iconImage!.image) : NSImageView(frame: NSRect.zero) + let imageView = NSImageView(frame: NSRect.zero) imageView.animates = false imageView.imageAlignment = .alignCenter imageView.imageScaling = .scaleProportionallyDown diff --git a/Shared/Data/SmallIconProvider.swift b/Shared/Data/SmallIconProvider.swift index 968a9767c..a070dec30 100644 --- a/Shared/Data/SmallIconProvider.swift +++ b/Shared/Data/SmallIconProvider.swift @@ -22,11 +22,7 @@ extension Feed: SmallIconProvider { if let iconImage = appDelegate.faviconDownloader.favicon(for: self) { return iconImage } - #if os(macOS) - return AppAssets.genericFeedImage - #else return FaviconGenerator.favicon(self) - #endif } } From ec2b23c9f05230364f66b7aa174a0148fc6b1c3d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 12 Nov 2019 15:52:07 -0600 Subject: [PATCH 182/237] Change Feed Inspector to use same IconView class as other places in code displaying feed icons. --- .../FeedInspectorViewController.swift | 20 ++++-------- Mac/Inspector/Inspector.storyboard | 31 +++++++++---------- Mac/MainWindow/IconView.swift | 1 + 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/Mac/Inspector/FeedInspectorViewController.swift b/Mac/Inspector/FeedInspectorViewController.swift index 18ac3e770..964668798 100644 --- a/Mac/Inspector/FeedInspectorViewController.swift +++ b/Mac/Inspector/FeedInspectorViewController.swift @@ -12,7 +12,7 @@ import Account final class FeedInspectorViewController: NSViewController, Inspector { - @IBOutlet weak var imageView: NSImageView? + @IBOutlet weak var iconView: IconView! @IBOutlet weak var nameTextField: NSTextField? @IBOutlet weak var homePageURLTextField: NSTextField? @IBOutlet weak var urlTextField: NSTextField? @@ -43,11 +43,7 @@ final class FeedInspectorViewController: NSViewController, Inspector { // MARK: NSViewController override func viewDidLoad() { - imageView!.wantsLayer = true - imageView!.layer?.cornerRadius = 4.0 - updateUI() - NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: nil) } @@ -101,25 +97,21 @@ private extension FeedInspectorViewController { } func updateImage() { - guard let feed = feed else { - imageView?.image = nil + guard let feed = feed, let iconView = iconView else { return } if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) { - imageView?.image = feedIcon.image + iconView.iconImage = feedIcon return } - if let favicon = appDelegate.faviconDownloader.favicon(for: feed)?.image { - if favicon.size.height < 16.0 && favicon.size.width < 16.0 { - favicon.size = NSSize(width: 16, height: 16) - } - imageView?.image = favicon + if let favicon = appDelegate.faviconDownloader.favicon(for: feed) { + iconView.iconImage = favicon return } - imageView?.image = feed.smallIcon?.image + iconView.iconImage = feed.smallIcon } func updateName() { diff --git a/Mac/Inspector/Inspector.storyboard b/Mac/Inspector/Inspector.storyboard index eb460de87..b35d220cf 100644 --- a/Mac/Inspector/Inspector.storyboard +++ b/Mac/Inspector/Inspector.storyboard @@ -1,8 +1,7 @@ - + - - + @@ -37,14 +36,6 @@ - - - - - - - - @@ -114,13 +105,19 @@ Field + + + + + + + - - + @@ -128,11 +125,12 @@ Field + - + @@ -142,7 +140,7 @@ Field - + @@ -151,7 +149,7 @@ Field - + @@ -294,7 +292,6 @@ Field - diff --git a/Mac/MainWindow/IconView.swift b/Mac/MainWindow/IconView.swift index 92d9f30dd..e03ea6be4 100644 --- a/Mac/MainWindow/IconView.swift +++ b/Mac/MainWindow/IconView.swift @@ -82,6 +82,7 @@ private extension IconView { func commonInit() { addSubview(imageView) wantsLayer = true + layer?.cornerRadius = 4.0 } func rectForImageView() -> NSRect { From 3d2806287a3ddd562566b338019f655e8c9300d4 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 12 Nov 2019 16:13:59 -0600 Subject: [PATCH 183/237] Fix to show display mode button on launch for iPad in portrait. Issue #1291 --- iOS/SceneCoordinator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index b9c60d2ef..e188f7370 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -318,7 +318,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) articleViewController.coordinator = self - let detailNavigationController = addNavControllerIfNecessary(articleViewController, showButton: false) + let detailNavigationController = addNavControllerIfNecessary(articleViewController, showButton: true) rootSplitViewController.showDetailViewController(detailNavigationController, sender: self) configureThreePanelMode(for: size) From 96dbd96527be8e7df90506893d3e737ab8309944 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 12 Nov 2019 19:24:07 -0600 Subject: [PATCH 184/237] Change keychain accessiblity to allow access when the device is locked. Issue #1292 --- .../Credentials/CredentialsManager.swift | 28 +++++++++---------- .../Feedbin/FeedbinAccountDelegate.swift | 20 ------------- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/Frameworks/Account/Credentials/CredentialsManager.swift b/Frameworks/Account/Credentials/CredentialsManager.swift index b52b6c30c..1043d9f6d 100644 --- a/Frameworks/Account/Credentials/CredentialsManager.swift +++ b/Frameworks/Account/Credentials/CredentialsManager.swift @@ -20,11 +20,12 @@ public struct CredentialsManager { }() public static func storeCredentials(_ credentials: Credentials, server: String) throws { - + var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword, - kSecAttrAccount as String: credentials.username, - kSecAttrServer as String: server] - + kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock, + kSecAttrAccount as String: credentials.username, + kSecAttrServer as String: server] + if credentials.type != .basic { query[kSecAttrSecurityDomain as String] = credentials.type.rawValue } @@ -32,26 +33,25 @@ public struct CredentialsManager { if let securityGroup = keychainGroup { query[kSecAttrAccessGroup as String] = securityGroup } - + let secretData = credentials.secret.data(using: String.Encoding.utf8)! - let attributes: [String: Any] = [kSecValueData as String: secretData] - let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary) - + query[kSecValueData as String] = secretData + + let status = SecItemAdd(query as CFDictionary, nil) + switch status { case errSecSuccess: return - case errSecItemNotFound: + case errSecDuplicateItem: break default: throw CredentialsError.unhandledError(status: status) } - guard status == errSecItemNotFound else { - return - } + var deleteQuery = query + deleteQuery.removeValue(forKey: kSecAttrAccessible as String) + SecItemDelete(deleteQuery as CFDictionary) - query[kSecValueData as String] = secretData - let addStatus = SecItemAdd(query as CFDictionary, nil) if addStatus != errSecSuccess { throw CredentialsError.unhandledError(status: status) diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index 50eb9356c..ea3027a4f 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -78,7 +78,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) refreshProgress.addToNumberOfTasksAndRemaining(5) @@ -112,7 +111,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { - retrieveCredentialsIfNecessary(account) os_log(.debug, log: log, "Sending article statuses...") @@ -169,7 +167,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { - retrieveCredentialsIfNecessary(account) os_log(.debug, log: log, "Refreshing article statuses...") @@ -216,7 +213,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) var fileData: Data? @@ -263,7 +259,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func addFolder(for account: Account, name: String, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) if let folder = account.ensureFolder(with: name) { completion(.success(folder)) } else { @@ -272,7 +267,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) guard folder.hasAtLeastOneFeed() else { folder.name = name @@ -300,7 +294,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) // Feedbin uses tags and if at least one feed isn't tagged, then the folder doesn't exist on their system guard folder.hasAtLeastOneFeed() else { @@ -364,7 +357,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) refreshProgress.addToNumberOfTasksAndRemaining(1) caller.createSubscription(url: url) { result in @@ -397,7 +389,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) // This error should never happen guard let subscriptionID = feed.subscriptionID else { @@ -433,7 +424,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) if from is Account { addFeed(for: account, with: feed, to: to, completion: completion) } else { @@ -449,7 +439,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) if let folder = container as? Folder, let feedID = Int(feed.feedID) { refreshProgress.addToNumberOfTasksAndRemaining(1) @@ -482,7 +471,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) if let existingFeed = account.existingFeed(withURL: feed.url) { account.addFeed(existingFeed, to: container) { result in @@ -507,7 +495,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) { - retrieveCredentialsIfNecessary(account) let group = DispatchGroup() @@ -536,7 +523,6 @@ final class FeedbinAccountDelegate: AccountDelegate { } func markArticles(for account: Account, articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) -> Set
? { - retrieveCredentialsIfNecessary(account) let syncStatuses = articles.map { article in return SyncStatus(articleID: article.articleID, key: statusKey, flag: flag) @@ -1308,11 +1294,5 @@ private extension FeedbinAccountDelegate { } } - - func retrieveCredentialsIfNecessary(_ account: Account) { - if credentials == nil { - credentials = try? account.retrieveCredentials(type: .basic) - } - } } From a89e5004175f4b228ef07ff816aa454cc50ae2d2 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 08:36:01 -0600 Subject: [PATCH 185/237] Updated to latest RSCore --- submodules/RSCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/RSCore b/submodules/RSCore index ff2072b8d..ba7bbb2ce 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit ff2072b8da8f3a716524e87165010301e78a72ab +Subproject commit ba7bbb2ce10ee04a730c0a1e425a1b2e9d338520 From 690608b1557da7a074442bfdb1b2f2e611d72474 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 08:44:22 -0600 Subject: [PATCH 186/237] Add Github Actions CI integration with Slack --- .github/workflows/build.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7ec5b8ce8..ae4d1a17a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,4 +49,14 @@ jobs: SCHEME: ${{ matrix.run-config['scheme'] }} DESTINATION: ${{ matrix.run-config['destination'] }} - run: buildscripts/ci-build.sh \ No newline at end of file + run: buildscripts/ci-build.sh + + - name: Notify Slack + uses: 8398a7/action-slack@v2.4.2 + with: + status: ${{ job.status }} + author_name: GitHub Actions CI + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: always() \ No newline at end of file From 8a0c3887ea7740de762d5d0debe0680a98979e27 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 09:07:14 -0600 Subject: [PATCH 187/237] Change to only notify slack on failure --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae4d1a17a..4c8984b2d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,4 +59,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - if: always() \ No newline at end of file + if: failure() \ No newline at end of file From 315ac3ee128c15059c37937ed3ea075a19bae138 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 09:36:05 -0600 Subject: [PATCH 188/237] Remove code that wasn't actually hiding the search bar --- iOS/MasterTimeline/MasterTimelineViewController.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index d340e7109..570f3bba4 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -79,13 +79,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } - override func viewWillAppear(_ animated: Bool) { - // Hide the search controller if we don't have any rows - if dataSource.snapshot().numberOfItems < 1 { - navigationItem.searchController?.isActive = false - } - } - // MARK: Actions @IBAction func markAllAsRead(_ sender: Any) { From 2fd2b8b1b040928441f1115cfa9c17e596d838c4 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 13:43:02 -0600 Subject: [PATCH 189/237] Update the refresh indicator since time when coming to the foreground. --- iOS/MasterFeed/MasterFeedViewController.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index f97341272..bdc953f6c 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -58,6 +58,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { NotificationCenter.default.addObserver(self, selector: #selector(feedMetadataDidChange(_:)), name: .FeedMetadataDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) refreshControl = UIRefreshControl() refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged) @@ -140,6 +141,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { applyChanges(animate: false) } + @objc func willEnterForeground(_ note: Notification) { + updateUI() + } + // MARK: Table View override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { From 38535910efb17ee8c929d5ded2efce09126af1a5 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 15:22:22 -0600 Subject: [PATCH 190/237] Change Select Feed to Go to Feed. Issue #1240 --- iOS/MasterFeed/MasterFeedViewController.swift | 16 +++--- .../MasterTimelineViewController.swift | 10 ++-- iOS/SceneCoordinator.swift | 51 ++++++++++--------- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index bdc953f6c..561a7ee20 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -65,11 +65,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { configureToolbar() becomeFirstResponder() + } override func viewWillAppear(_ animated: Bool) { - navigationController?.title = NSLocalizedString("Feeds", comment: "Feeds") - applyChanges(animate: false) updateUI() super.viewWillAppear(animated) } @@ -101,7 +100,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } if let node = node, dataSource.indexPath(for: node) != nil { - reloadNode(node) + // This can stop the scrolling of the disclose function if we don't delay it enough to complete + DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) { + self.reloadNode(node) + } } } @@ -462,14 +464,14 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } } - func updateFeedSelection() { + func updateFeedSelection(animated: Bool) { if dataSource.snapshot().numberOfItems > 0 { if let indexPath = coordinator.currentFeedIndexPath { if tableView.indexPathForSelectedRow != indexPath { - tableView.selectRowAndScrollIfNotVisible(at: indexPath, animated: true) + tableView.selectRowAndScrollIfNotVisible(at: indexPath, animated: animated) } } else { - tableView.selectRow(at: nil, animated: true, scrollPosition: .none) + tableView.selectRow(at: nil, animated: animated, scrollPosition: .none) } } } @@ -1022,7 +1024,7 @@ private extension MasterFeedViewController { deleteCommand.perform() if indexPath == coordinator.currentFeedIndexPath { - coordinator.selectFeed(nil) + coordinator.selectFeed(nil, animated: false) } } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 570f3bba4..6990a53a4 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -592,10 +592,9 @@ private extension MasterTimelineViewController { func discloseFeedAction(_ article: Article) -> UIAction? { guard let feed = article.feed else { return nil } - let title = NSLocalizedString("Select Feed", comment: "Select Feed") + let title = NSLocalizedString("Go to Feed", comment: "Go to Feed") let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in - self?.coordinator.selectFeed(nil, animated: true) - self?.coordinator.discloseFeed(feed, animated: true) + self?.coordinator.discloseFeed(feed, animated: false) } return action } @@ -603,10 +602,9 @@ private extension MasterTimelineViewController { func discloseFeedAlertAction(_ article: Article, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? { guard let feed = article.feed else { return nil } - let title = NSLocalizedString("Select Feed", comment: "Select Feed") + let title = NSLocalizedString("Go to Feed", comment: "Go to Feed") let action = UIAlertAction(title: title, style: .default) { [weak self] action in - self?.coordinator.selectFeed(nil, animated: true) - self?.coordinator.discloseFeed(feed, animated: true) + self?.coordinator.discloseFeed(feed, animated: false) completionHandler(true) } return action diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index e188f7370..5c9d46b61 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -89,9 +89,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } private let treeControllerDelegate = FeedTreeControllerDelegate() - private lazy var treeController: TreeController = { - return TreeController(delegate: treeControllerDelegate) - }() + private let treeController: TreeController var stateRestorationActivity: NSUserActivity? { return activityManager.stateRestorationActivity @@ -280,6 +278,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } override init() { + treeController = TreeController(delegate: treeControllerDelegate) + super.init() for section in treeController.rootNode.childNodes { @@ -315,6 +315,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { masterFeedViewController = UIStoryboard.main.instantiateController(ofType: MasterFeedViewController.self) masterFeedViewController.coordinator = self masterNavigationController.pushViewController(masterFeedViewController, animated: false) + masterFeedViewController.reloadFeeds() let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) articleViewController.coordinator = self @@ -327,7 +328,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func handle(_ activity: NSUserActivity) { - selectFeed(nil) + selectFeed(nil, animated: false) guard let activityType = ActivityType(rawValue: activity.activityType) else { return } switch activityType { @@ -371,12 +372,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func selectFirstUnreadInAllUnread() { - selectFeed(IndexPath(row: 1, section: 0)) + selectFeed(IndexPath(row: 1, section: 0), animated: false) selectFirstUnreadArticleInTimeline() } func showSearch() { - selectFeed(nil) + selectFeed(nil, animated: false) installTimelineControllerIfNecessary(animated: false) DispatchQueue.main.asyncAfter(deadline: .now()) { self.masterTimelineViewController!.showSearchAll() @@ -532,13 +533,13 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { return indexPathFor(node) } - func selectFeed(_ indexPath: IndexPath?, animated: Bool = false) { + func selectFeed(_ indexPath: IndexPath?, animated: Bool) { guard indexPath != currentFeedIndexPath else { return } selectArticle(nil) currentFeedIndexPath = indexPath - masterFeedViewController.updateFeedSelection() + masterFeedViewController.updateFeedSelection(animated: animated) if let ip = indexPath, let node = nodeFor(ip), let fetcher = node.representedObject as? ArticleFetcher { timelineFetcher = fetcher @@ -556,31 +557,31 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { func selectPrevFeed() { if let indexPath = prevFeedIndexPath { - selectFeed(indexPath) + selectFeed(indexPath, animated: true) } } func selectNextFeed() { if let indexPath = nextFeedIndexPath { - selectFeed(indexPath) + selectFeed(indexPath, animated: true) } } func selectTodayFeed() { masterFeedViewController?.ensureSectionIsExpanded(0) { - self.selectFeed(IndexPath(row: 0, section: 0)) + self.selectFeed(IndexPath(row: 0, section: 0), animated: true) } } func selectAllUnreadFeed() { masterFeedViewController?.ensureSectionIsExpanded(0) { - self.selectFeed(IndexPath(row: 1, section: 0)) + self.selectFeed(IndexPath(row: 1, section: 0), animated: true) } } func selectStarredFeed() { masterFeedViewController?.ensureSectionIsExpanded(0) { - self.selectFeed(IndexPath(row: 2, section: 0)) + self.selectFeed(IndexPath(row: 2, section: 0), animated: true) } } @@ -830,7 +831,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func showAdd(_ type: AddControllerType, initialFeed: String? = nil, initialFeedName: String? = nil) { - selectFeed(nil) + selectFeed(nil, animated: false) let addViewController = UIStoryboard.add.instantiateInitialViewController() as! UINavigationController @@ -969,7 +970,7 @@ extension SceneCoordinator: UINavigationControllerDelegate { // If we are showing the Feeds and only the feeds start clearing stuff if viewController === masterFeedViewController && !isThreePanelMode && !isTimelineViewControllerPending { activityManager.invalidateCurrentActivities() - selectFeed(nil) + selectFeed(nil, animated: true) return } @@ -1204,7 +1205,7 @@ private extension SceneCoordinator { } if unreadCountProvider.unreadCount > 0 { - selectFeed(prevIndexPath) + selectFeed(prevIndexPath, animated: true) return true } @@ -1310,7 +1311,7 @@ private extension SceneCoordinator { } if unreadCountProvider.unreadCount > 0 { - selectFeed(nextIndexPath) + selectFeed(nextIndexPath, animated: true) return true } @@ -1492,13 +1493,15 @@ private extension SceneCoordinator { // MARK: Double Split func installTimelineControllerIfNecessary(animated: Bool) { - - isTimelineViewControllerPending = true - if navControllerForTimeline().viewControllers.filter({ $0 is MasterTimelineViewController }).count < 1 { + + isTimelineViewControllerPending = true + masterTimelineViewController = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self) masterTimelineViewController!.coordinator = self navControllerForTimeline().pushViewController(masterTimelineViewController!, animated: animated) + + masterTimelineViewController?.reloadArticles(animate: false) } } @@ -1646,19 +1649,19 @@ private extension SceneCoordinator { func handleSelectToday() { if let indexPath = indexPathFor(SmartFeedsController.shared.todayFeed) { - selectFeed(indexPath) + selectFeed(indexPath, animated: false) } } func handleSelectAllUnread() { if let indexPath = indexPathFor(SmartFeedsController.shared.unreadFeed) { - selectFeed(indexPath) + selectFeed(indexPath, animated: false) } } func handleSelectStarred() { if let indexPath = indexPathFor(SmartFeedsController.shared.starredFeed) { - selectFeed(indexPath) + selectFeed(indexPath, animated: false) } } @@ -1667,7 +1670,7 @@ private extension SceneCoordinator { return } if let indexPath = indexPathFor(folderNode) { - selectFeed(indexPath) + selectFeed(indexPath, animated: false) } } From ef84acc02d97e4f319947a50cfafc3824eb8c838 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 15:31:14 -0600 Subject: [PATCH 191/237] Implement better scroll interference avoidance strategy. --- iOS/MasterFeed/MasterFeedViewController.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 561a7ee20..334ebbeff 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -99,11 +99,17 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { node = coordinator.rootNode.descendantNodeRepresentingObject(representedObject as AnyObject) } - if let node = node, dataSource.indexPath(for: node) != nil { - // This can stop the scrolling of the disclose function if we don't delay it enough to complete - DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) { + // Only do the reload of the node when absolutely necessary. It can stop programatic scrolling from + // 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 { + + if cell.unreadCount != unreadCountProvider.unreadCount { self.reloadNode(node) } + } } From 0c33f6c496e324d4bb6c650d06cf32cd28e86a2e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 15:41:41 -0600 Subject: [PATCH 192/237] Animate disclosing the feed when using Go to Feed. --- iOS/MasterTimeline/MasterTimelineViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 6990a53a4..c1613e66d 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -594,7 +594,7 @@ private extension MasterTimelineViewController { let title = NSLocalizedString("Go to Feed", comment: "Go to Feed") let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in - self?.coordinator.discloseFeed(feed, animated: false) + self?.coordinator.discloseFeed(feed, animated: true) } return action } @@ -604,7 +604,7 @@ private extension MasterTimelineViewController { let title = NSLocalizedString("Go to Feed", comment: "Go to Feed") let action = UIAlertAction(title: title, style: .default) { [weak self] action in - self?.coordinator.discloseFeed(feed, animated: false) + self?.coordinator.discloseFeed(feed, animated: true) completionHandler(true) } return action From 179cce177cca4c4100c7a8df92551a643baf968d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 17:02:14 -0600 Subject: [PATCH 193/237] Fix Feed Inspector form size on iPad --- iOS/Inspector/FeedInspectorViewController.swift | 2 +- iOS/SceneCoordinator.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/Inspector/FeedInspectorViewController.swift b/iOS/Inspector/FeedInspectorViewController.swift index 2aa9dab67..d52a8275f 100644 --- a/iOS/Inspector/FeedInspectorViewController.swift +++ b/iOS/Inspector/FeedInspectorViewController.swift @@ -11,7 +11,7 @@ import Account class FeedInspectorViewController: UITableViewController { - static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 400.0) + static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 500.0) var feed: Feed! @IBOutlet weak var nameTextField: UITextField! diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 5c9d46b61..d84c0636a 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -824,8 +824,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { let feedInspectorNavController = UIStoryboard.inspector.instantiateViewController(identifier: "FeedInspectorNavigationViewController") as! UINavigationController let feedInspectorController = feedInspectorNavController.topViewController as! FeedInspectorViewController - feedInspectorController.modalPresentationStyle = .formSheet - feedInspectorController.preferredContentSize = FeedInspectorViewController.preferredContentSizeForFormSheetDisplay + feedInspectorNavController.modalPresentationStyle = .formSheet + feedInspectorNavController.preferredContentSize = FeedInspectorViewController.preferredContentSizeForFormSheetDisplay feedInspectorController.feed = feed rootSplitViewController.present(feedInspectorNavController, animated: true) } From 48fef65bc4ea194e86f496190ed8c7a75adc4c27 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 13 Nov 2019 17:13:06 -0600 Subject: [PATCH 194/237] Remove refresh interval setting. Issue #1293 --- NetNewsWire.xcodeproj/project.pbxproj | 6 - iOS/AppDefaults.swift | 12 -- iOS/AppDelegate.swift | 4 +- .../RefreshIntervalViewController.swift | 111 ------------------ iOS/Settings/Settings.storyboard | 83 +++---------- iOS/Settings/SettingsViewController.swift | 17 +-- 6 files changed, 19 insertions(+), 214 deletions(-) delete mode 100644 iOS/Settings/RefreshIntervalViewController.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 1c8df4ab1..819da2fc1 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -108,7 +108,6 @@ 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 5183CCE6226F4E110010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 5183CCE8226F68D90010922C /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; - 5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; 518651B223555EB20078E021 /* NNW3Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651AB23555EB20078E021 /* NNW3Document.swift */; }; 518651DA235621840078E021 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651D9235621840078E021 /* ImageTransition.swift */; }; 5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5186A634235EF3A800C97195 /* VibrantLabel.swift */; }; @@ -121,7 +120,6 @@ 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; - 51A16997235E10D700EB091F /* RefreshIntervalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */; }; 51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */; }; 51A1699A235E10D700EB091F /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51A16990235E10D600EB091F /* Settings.storyboard */; }; 51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */; }; @@ -1280,7 +1278,6 @@ 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.swift; sourceTree = ""; }; 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalAccountViewController.swift; sourceTree = ""; }; 51A16990235E10D600EB091F /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountInspectorViewController.swift; sourceTree = ""; }; @@ -1798,7 +1795,6 @@ 51A16990235E10D600EB091F /* Settings.storyboard */, 51A16995235E10D600EB091F /* AboutViewController.swift */, 51A16992235E10D600EB091F /* AddAccountViewController.swift */, - 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */, 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */, 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */, 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */, @@ -3963,14 +3959,12 @@ 51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */, 51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */, 5108F6D823763094001ABC45 /* TickMarkSlider.swift in Sources */, - 5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */, 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */, 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */, 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */, 5108F6B72375E612001ABC45 /* CacheCleaner.swift in Sources */, 518651DA235621840078E021 /* ImageTransition.swift in Sources */, 514219372352510100E07E2C /* ImageScrollView.swift in Sources */, - 51A16997235E10D700EB091F /* RefreshIntervalViewController.swift in Sources */, 516AE9B32371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index d68d1bd48..e2e576835 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -24,7 +24,6 @@ struct AppDefaults { static let timelineIconSize = "timelineIconSize" static let timelineSortDirection = "timelineSortDirection" static let displayUndoAvailableTip = "displayUndoAvailableTip" - static let refreshInterval = "refreshInterval" static let lastRefresh = "lastRefresh" } @@ -45,16 +44,6 @@ struct AppDefaults { } } - static var refreshInterval: RefreshInterval { - get { - let rawValue = AppDefaults.shared.integer(forKey: Key.refreshInterval) - return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour - } - set { - AppDefaults.shared.set(newValue.rawValue, forKey: Key.refreshInterval) - } - } - static var timelineGroupByFeed: Bool { get { return bool(for: Key.timelineGroupByFeed) @@ -112,7 +101,6 @@ struct AppDefaults { static func registerDefaults() { let defaults: [String : Any] = [Key.lastImageCacheFlushDate: Date(), - Key.refreshInterval: RefreshInterval.everyHour.rawValue, Key.timelineGroupByFeed: false, Key.timelineNumberOfLines: 2, Key.timelineIconSize: MasterTimelineIconSize.medium.rawValue, diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index 58c741ea4..8fc666e1b 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -290,9 +290,9 @@ private extension AppDelegate { /// Schedules a background app refresh based on `AppDefaults.refreshInterval`. func scheduleBackgroundFeedRefresh() { let request = BGAppRefreshTaskRequest(identifier: "com.ranchero.NetNewsWire.FeedRefresh") - request.earliestBeginDate = Date(timeIntervalSinceNow: AppDefaults.refreshInterval.inSeconds()) + request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) - // We send this to a dedicated seria queue because as of 11/05/19 on iOS 13.2 the call to the + // We send this to a dedicated serial queue because as of 11/05/19 on iOS 13.2 the call to the // task scheduler can hang indefinitely. bgTaskDispatchQueue.async { do { diff --git a/iOS/Settings/RefreshIntervalViewController.swift b/iOS/Settings/RefreshIntervalViewController.swift deleted file mode 100644 index 8f3d89ba9..000000000 --- a/iOS/Settings/RefreshIntervalViewController.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// RefreshIntervalViewController.swift -// NetNewsWire -// -// Created by Maurice Parker on 4/25/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import UIKit - -class RefreshIntervalViewController: UITableViewController { - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 7 - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) - - cell.textLabel?.adjustsFontForContentSizeCategory = true - - let userRefreshInterval = AppDefaults.refreshInterval - - switch indexPath.row { - case 0: - cell.textLabel?.text = RefreshInterval.manually.description() - if userRefreshInterval == RefreshInterval.manually { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - case 1: - cell.textLabel?.text = RefreshInterval.every10Minutes.description() - if userRefreshInterval == RefreshInterval.every10Minutes { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - case 2: - cell.textLabel?.text = RefreshInterval.every30Minutes.description() - if userRefreshInterval == RefreshInterval.every30Minutes { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - case 3: - cell.textLabel?.text = RefreshInterval.everyHour.description() - if userRefreshInterval == RefreshInterval.everyHour { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - case 4: - cell.textLabel?.text = RefreshInterval.every2Hours.description() - if userRefreshInterval == RefreshInterval.every2Hours { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - case 5: - cell.textLabel?.text = RefreshInterval.every4Hours.description() - if userRefreshInterval == RefreshInterval.every4Hours { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - default: - cell.textLabel?.text = RefreshInterval.every8Hours.description() - if userRefreshInterval == RefreshInterval.every8Hours { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - } - - return cell - - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - - let refreshInterval: RefreshInterval - - switch indexPath.row { - case 0: - refreshInterval = RefreshInterval.manually - case 1: - refreshInterval = RefreshInterval.every10Minutes - case 2: - refreshInterval = RefreshInterval.every30Minutes - case 3: - refreshInterval = RefreshInterval.everyHour - case 4: - refreshInterval = RefreshInterval.every2Hours - case 5: - refreshInterval = RefreshInterval.every4Hours - default: - refreshInterval = RefreshInterval.every8Hours - } - - AppDefaults.refreshInterval = refreshInterval - self.navigationController?.popViewController(animated: true) - - } - -} diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index ed9e460df..bcb9ff294 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -60,32 +60,8 @@ - - - - - - - - - - - - - + @@ -102,7 +78,7 @@ - + @@ -119,7 +95,7 @@ - + @@ -140,7 +116,7 @@ - + @@ -171,7 +147,7 @@ - + @@ -202,7 +178,7 @@ - + @@ -223,7 +199,7 @@ - + @@ -240,7 +216,7 @@ - + @@ -257,7 +233,7 @@ - + @@ -274,7 +250,7 @@ - + @@ -291,7 +267,7 @@ - + @@ -308,7 +284,7 @@ - + @@ -474,35 +450,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -680,7 +627,7 @@ - + @@ -783,7 +730,7 @@ - + @@ -839,7 +786,7 @@ - + diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift index 2a36606c8..ec690552a 100644 --- a/iOS/Settings/SettingsViewController.swift +++ b/iOS/Settings/SettingsViewController.swift @@ -104,16 +104,6 @@ class SettingsViewController: UITableViewController { cell = acctCell } - case 2: - - if indexPath.row == 0 { - cell = tableView.dequeueReusableCell(withIdentifier: "SettingsTableViewCell", for: indexPath) - cell.textLabel?.text = NSLocalizedString("Refresh Interval", comment: "Refresh Interval") - cell.detailTextLabel?.text = AppDefaults.refreshInterval.description() - } else { - cell = super.tableView(tableView, cellForRowAt: indexPath) - } - default: cell = super.tableView(tableView, cellForRowAt: indexPath) @@ -141,21 +131,18 @@ class SettingsViewController: UITableViewController { case 2: switch indexPath.row { case 0: - let timeline = UIStoryboard.settings.instantiateController(ofType: RefreshIntervalViewController.self) - self.navigationController?.pushViewController(timeline, animated: true) - case 1: tableView.selectRow(at: nil, animated: true, scrollPosition: .none) if let sourceView = tableView.cellForRow(at: indexPath) { let sourceRect = tableView.rectForRow(at: indexPath) importOPML(sourceView: sourceView, sourceRect: sourceRect) } - case 2: + case 1: tableView.selectRow(at: nil, animated: true, scrollPosition: .none) if let sourceView = tableView.cellForRow(at: indexPath) { let sourceRect = tableView.rectForRow(at: indexPath) exportOPML(sourceView: sourceView, sourceRect: sourceRect) } - case 3: + case 2: addFeed() tableView.selectRow(at: nil, animated: true, scrollPosition: .none) default: From eb6192d62b1cf394c98f9c66cf6ba7b0bc2b8f1d Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 13 Nov 2019 21:59:42 -0800 Subject: [PATCH 195/237] Bump iOS build number. --- xcconfig/common/NetNewsWire_ios_target_common.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index dffc3a7a0..938a898cc 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it MARKETING_VERSION = 5.0 -CURRENT_PROJECT_VERSION = 6 +CURRENT_PROJECT_VERSION = 7 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon From 3ed5a43de3af604abf7e599972eb47a6ab0dbf6b Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Fri, 15 Nov 2019 07:59:44 +1100 Subject: [PATCH 196/237] Improves the behaviour and fixes some issues with cancelling of Feedly operations. --- .../Feedly/Operations/FeedlyOperation.swift | 14 ++++++++------ .../Feedly/Operations/FeedlySyncAllOperation.swift | 5 ++--- .../FeedlySyncStarredArticlesOperation.swift | 9 ++++++++- .../FeedlySyncStreamContentsOperation.swift | 9 ++++++++- .../FeedlySyncUnreadStatusesOperation.swift | 9 ++++++++- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Frameworks/Account/Feedly/Operations/FeedlyOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlyOperation.swift index 8c231bc53..5c6b16afc 100644 --- a/Frameworks/Account/Feedly/Operations/FeedlyOperation.swift +++ b/Frameworks/Account/Feedly/Operations/FeedlyOperation.swift @@ -21,8 +21,8 @@ class FeedlyOperation: Operation { func didFinish() { assert(Thread.isMainThread) assert(!isFinished, "Finished operation is attempting to finish again.") - self.isExecutingOperation = false - self.isFinishedOperation = true + isExecutingOperation = false + isFinishedOperation = true } func didFinish(_ error: Error) { @@ -33,16 +33,18 @@ class FeedlyOperation: Operation { } override func start() { + guard !isCancelled else { + isExecutingOperation = false + isFinishedOperation = true + return + } + isExecutingOperation = true DispatchQueue.main.async { self.main() } } - override func cancel() { - super.cancel() - } - override var isExecuting: Bool { return isExecutingOperation } diff --git a/Frameworks/Account/Feedly/Operations/FeedlySyncAllOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlySyncAllOperation.swift index 43faee339..b93031677 100644 --- a/Frameworks/Account/Feedly/Operations/FeedlySyncAllOperation.swift +++ b/Frameworks/Account/Feedly/Operations/FeedlySyncAllOperation.swift @@ -94,10 +94,9 @@ final class FeedlySyncAllOperation: FeedlyOperation { os_log(.debug, log: log, "Cancelling sync %{public}@", syncUUID.uuidString) self.operationQueue.cancelAllOperations() - syncCompletionHandler?(.failure(URLError(.cancelled))) - syncCompletionHandler = nil + super.cancel() - self.didFinish() + didFinish() } override func main() { diff --git a/Frameworks/Account/Feedly/Operations/FeedlySyncStarredArticlesOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlySyncStarredArticlesOperation.swift index c4955362b..ea608ccbb 100644 --- a/Frameworks/Account/Feedly/Operations/FeedlySyncStarredArticlesOperation.swift +++ b/Frameworks/Account/Feedly/Operations/FeedlySyncStarredArticlesOperation.swift @@ -96,13 +96,15 @@ final class FeedlySyncStarredArticlesOperation: FeedlyOperation, FeedlyOperation } override func cancel() { + os_log(.debug, log: log, "Canceling sync starred articles") operationQueue.cancelAllOperations() super.cancel() + didFinish() } override func main() { guard !isCancelled else { - didFinish() + // override of cancel calls didFinish(). return } @@ -110,6 +112,11 @@ final class FeedlySyncStarredArticlesOperation: FeedlyOperation, FeedlyOperation } func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) { + guard !isCancelled else { + os_log(.debug, log: log, "Cancelled starred stream contents for %@", stream.id) + return + } + entryProvider.addEntries(from: operation) os_log(.debug, log: log, "Collecting %i items from %@", stream.items.count, stream.id) diff --git a/Frameworks/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift index 2d416b697..667857073 100644 --- a/Frameworks/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift +++ b/Frameworks/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift @@ -42,13 +42,15 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD } override func cancel() { + os_log(.debug, log: log, "Canceling sync stream contents") operationQueue.cancelAllOperations() super.cancel() + didFinish() } override func main() { guard !isCancelled else { - didFinish() + // override of cancel calls didFinish(). return } @@ -92,6 +94,11 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD } func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) { + guard !isCancelled else { + os_log(.debug, log: log, "Cancelled requesting page for %@", resource.id) + return + } + os_log(.debug, log: log, "Ingesting %i items from %@", stream.items.count, stream.id) guard let continuation = stream.continuation else { diff --git a/Frameworks/Account/Feedly/Operations/FeedlySyncUnreadStatusesOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlySyncUnreadStatusesOperation.swift index 15280aa44..6cae9d468 100644 --- a/Frameworks/Account/Feedly/Operations/FeedlySyncUnreadStatusesOperation.swift +++ b/Frameworks/Account/Feedly/Operations/FeedlySyncUnreadStatusesOperation.swift @@ -77,13 +77,15 @@ final class FeedlySyncUnreadStatusesOperation: FeedlyOperation, FeedlyOperationD } override func cancel() { + os_log(.debug, log: log, "Canceling sync unread statuses") operationQueue.cancelAllOperations() super.cancel() + didFinish() } override func main() { guard !isCancelled else { - didFinish() + // override of cancel calls didFinish(). return } @@ -91,6 +93,11 @@ final class FeedlySyncUnreadStatusesOperation: FeedlyOperation, FeedlyOperationD } func feedlyGetStreamIdsOperation(_ operation: FeedlyGetStreamIdsOperation, didGet streamIds: FeedlyStreamIds) { + guard !isCancelled else { + os_log(.debug, log: log, "Cancelled unread stream ids.") + return + } + os_log(.debug, log: log, "Collecting %i unread article ids from %@", streamIds.ids.count, resource.id) unreadEntryIdsProvider.addEntryIds(from: operation) From 0d66259650ee4d43dd19d3f5693370779a4eef9e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 14 Nov 2019 15:06:32 -0600 Subject: [PATCH 197/237] Add ArticleFetcherType and change state restoration to use new type. --- .../Account/Account.xcodeproj/project.pbxproj | 8 +- Frameworks/Account/ArticleFetcher.swift | 20 ++- Frameworks/Account/ArticleFetcherType.swift | 79 ++++++++++ Frameworks/Account/DeepLinkProvider.swift | 21 --- Frameworks/Account/Feed.swift | 12 +- Frameworks/Account/Folder.swift | 12 +- NetNewsWire.xcodeproj/project.pbxproj | 8 +- Shared/Activity/ActivityManager.swift | 99 ++++++------- Shared/Activity/ActivityType.swift | 10 +- Shared/AppNotifications.swift | 15 -- Shared/Data/ArticleUtilities.swift | 21 ++- Shared/SmartFeeds/SearchFeedDelegate.swift | 4 + .../SearchTimelineFeedDelegate.swift | 4 + Shared/SmartFeeds/SmartFeed.swift | 4 + Shared/SmartFeeds/SmartFeedsController.swift | 15 +- Shared/SmartFeeds/StarredFeedDelegate.swift | 4 + Shared/SmartFeeds/TodayFeedDelegate.swift | 4 + Shared/SmartFeeds/UnreadFeed.swift | 4 + Shared/UserInfoKey.swift | 26 ++++ .../UserNotificationManager.swift | 2 +- iOS/Resources/Info.plist | 10 +- iOS/SceneCoordinator.swift | 138 +++++++----------- 22 files changed, 290 insertions(+), 230 deletions(-) create mode 100644 Frameworks/Account/ArticleFetcherType.swift delete mode 100644 Frameworks/Account/DeepLinkProvider.swift create mode 100644 Shared/UserInfoKey.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 8044c4679..5f793a3c2 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; }; 510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; }; 510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */; }; + 511B9804237CD4270028BCAA /* ArticleFetcherType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */; }; 513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; }; 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; }; 5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; }; @@ -41,7 +42,6 @@ 51E490362288C37100C791F0 /* FeedbinDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E490352288C37100C791F0 /* FeedbinDate.swift */; }; 51E59599228C77BC00FCC42B /* FeedbinUnreadEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E59598228C77BC00FCC42B /* FeedbinUnreadEntry.swift */; }; 51E5959B228C781500FCC42B /* FeedbinStarredEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E5959A228C781500FCC42B /* FeedbinStarredEntry.swift */; }; - 51FE1008234635A20056195D /* DeepLinkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE1007234635A20056195D /* DeepLinkProvider.swift */; }; 552032F8229D5D5A009559E0 /* ReaderAPIEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 552032ED229D5D5A009559E0 /* ReaderAPIEntry.swift */; }; 552032F9229D5D5A009559E0 /* ReaderAPISubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 552032EE229D5D5A009559E0 /* ReaderAPISubscription.swift */; }; 552032FB229D5D5A009559E0 /* ReaderAPITag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 552032F0229D5D5A009559E0 /* ReaderAPITag.swift */; }; @@ -210,6 +210,7 @@ 5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = ""; }; 510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = ""; }; 510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedMetadataFile.swift; sourceTree = ""; }; + 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleFetcherType.swift; sourceTree = ""; }; 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = ""; }; 513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = ""; }; 5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = ""; }; @@ -240,7 +241,6 @@ 51E490352288C37100C791F0 /* FeedbinDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinDate.swift; sourceTree = ""; }; 51E59598228C77BC00FCC42B /* FeedbinUnreadEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinUnreadEntry.swift; sourceTree = ""; }; 51E5959A228C781500FCC42B /* FeedbinStarredEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinStarredEntry.swift; sourceTree = ""; }; - 51FE1007234635A20056195D /* DeepLinkProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkProvider.swift; sourceTree = ""; }; 552032ED229D5D5A009559E0 /* ReaderAPIEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReaderAPIEntry.swift; sourceTree = ""; }; 552032EE229D5D5A009559E0 /* ReaderAPISubscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReaderAPISubscription.swift; sourceTree = ""; }; 552032F0229D5D5A009559E0 /* ReaderAPITag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReaderAPITag.swift; sourceTree = ""; }; @@ -527,6 +527,7 @@ 84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */, 510BD110232C3801002692E4 /* AccountMetadataFile.swift */, 84F73CF0202788D80000BCEF /* ArticleFetcher.swift */, + 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */, 84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */, 8419740D1F6DD25F006346C4 /* Container.swift */, 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */, @@ -536,7 +537,6 @@ 510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */, 841974001F6DD1EC006346C4 /* Folder.swift */, 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */, - 51FE1007234635A20056195D /* DeepLinkProvider.swift */, 5165D71F22835E9800D9D53D /* FeedFinder */, 515E4EB12324FF7D0057B0E7 /* Credentials */, 8419742B1F6DDE84006346C4 /* LocalAccount */, @@ -960,6 +960,7 @@ 9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */, 552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */, 9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */, + 511B9804237CD4270028BCAA /* ArticleFetcherType.swift in Sources */, 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */, 9E713653233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift in Sources */, 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, @@ -1013,7 +1014,6 @@ 9E1773D923458D590056A5A8 /* FeedlyResourceId.swift in Sources */, 9EE4CCFA234F106600FBAE4B /* FeedlyFeedContainerValidator.swift in Sources */, 552032FC229D5D5A009559E0 /* ReaderAPIUnreadEntry.swift in Sources */, - 51FE1008234635A20056195D /* DeepLinkProvider.swift in Sources */, 9EC688EA232B973C00A8D0A2 /* FeedlyAPICaller.swift in Sources */, 9E1773D32345700F0056A5A8 /* FeedlyLink.swift in Sources */, 9EAEC62823331C350085D7C9 /* FeedlyCategory.swift in Sources */, diff --git a/Frameworks/Account/ArticleFetcher.swift b/Frameworks/Account/ArticleFetcher.swift index 4a5987a56..a4bb9c015 100644 --- a/Frameworks/Account/ArticleFetcher.swift +++ b/Frameworks/Account/ArticleFetcher.swift @@ -11,6 +11,8 @@ import Articles public protocol ArticleFetcher { + var articleFetcherType: ArticleFetcherType? { get } + func fetchArticles() -> Set
func fetchArticlesAsync(_ callback: @escaping ArticleSetBlock) func fetchUnreadArticles() -> Set
@@ -18,7 +20,15 @@ public protocol ArticleFetcher { } extension Feed: ArticleFetcher { - + + public var articleFetcherType: ArticleFetcherType? { + guard let accountID = account?.accountID else { + assertionFailure("Expected feed.account, but got nil.") + return nil + } + return ArticleFetcherType.feed(accountID, feedID) + } + public func fetchArticles() -> Set
{ return account?.fetchArticles(.feed(self)) ?? Set
() } @@ -48,6 +58,14 @@ extension Feed: ArticleFetcher { extension Folder: ArticleFetcher { + public var articleFetcherType: ArticleFetcherType? { + guard let accountID = account?.accountID else { + assertionFailure("Expected feed.account, but got nil.") + return nil + } + return ArticleFetcherType.folder(accountID, nameForDisplay) + } + public func fetchArticles() -> Set
{ return fetchUnreadArticles() } diff --git a/Frameworks/Account/ArticleFetcherType.swift b/Frameworks/Account/ArticleFetcherType.swift new file mode 100644 index 000000000..b030c5da4 --- /dev/null +++ b/Frameworks/Account/ArticleFetcherType.swift @@ -0,0 +1,79 @@ +// +// ArticleFetcherType.swift +// Account +// +// Created by Maurice Parker on 11/13/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +public enum ArticleFetcherType: CustomStringConvertible { + + case smartFeed(String) // String is a unique identifier + case script(String) // String is a unique identifier + case feed(String, String) // accountID, feedID + case folder(String, String) // accountID, folderName + + public var description: String { + switch self { + case .smartFeed(let id): + return "smartFeed: \(id)" + case .script(let id): + return "script: \(id)" + case .feed(let accountID, let feedID): + return "feed: \(accountID)_\(feedID)" + case .folder(let accountID, let folderName): + return "folder: \(accountID)_\(folderName)" + } + } + + public var userInfo: [AnyHashable: Any] { + switch self { + case .smartFeed(let id): + return [ + "type": "smartFeed", + "id": id + ] + case .script(let id): + return [ + "type": "script", + "id": id + ] + case .feed(let accountID, let feedID): + return [ + "type": "feed", + "accountID": accountID, + "feedID": feedID + ] + case .folder(let accountID, let folderName): + return [ + "type": "folder", + "accountID": accountID, + "folderName": folderName + ] + } + } + + public init?(userInfo: [AnyHashable: Any]) { + guard let type = userInfo["type"] as? String else { return nil } + + switch type { + case "smartFeed": + guard let id = userInfo["id"] as? String else { return nil } + self = ArticleFetcherType.smartFeed(id) + case "script": + guard let id = userInfo["id"] as? String else { return nil } + self = ArticleFetcherType.script(id) + case "feed": + guard let accountID = userInfo["accountID"] as? String, let feedID = userInfo["feedID"] as? String else { return nil } + self = ArticleFetcherType.feed(accountID, feedID) + case "folder": + guard let accountID = userInfo["accountID"] as? String, let folderName = userInfo["folderName"] as? String else { return nil } + self = ArticleFetcherType.folder(accountID, folderName) + default: + return nil + } + } + +} diff --git a/Frameworks/Account/DeepLinkProvider.swift b/Frameworks/Account/DeepLinkProvider.swift deleted file mode 100644 index 5b97d2244..000000000 --- a/Frameworks/Account/DeepLinkProvider.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// DeepLinkProvider.swift -// Account -// -// Created by Maurice Parker on 10/3/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public enum DeepLinkKey: String { - case accountID = "accountID" - case accountName = "accountName" - case feedID = "feedID" - case articleID = "articleID" - case folderName = "folderName" -} - -public protocol DeepLinkProvider { - var deepLinkUserInfo: [AnyHashable : Any] { get } -} diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/Feed.swift index cd3a17ec1..c2855ef30 100644 --- a/Frameworks/Account/Feed.swift +++ b/Frameworks/Account/Feed.swift @@ -11,7 +11,7 @@ import RSCore import RSWeb import Articles -public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, DeepLinkProvider, Hashable { +public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Hashable { public weak var account: Account? public let url: String @@ -179,16 +179,6 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, De account.renameFeed(self, to: newName, completion: completion) } - // MARK: - DeepLinkProvider - - public var deepLinkUserInfo: [AnyHashable : Any] { - return [ - DeepLinkKey.accountID.rawValue: account?.accountID ?? "", - DeepLinkKey.accountName.rawValue: account?.nameForDisplay ?? "", - DeepLinkKey.feedID.rawValue: feedID - ] - } - // MARK: - UnreadCountProvider public var unreadCount: Int { diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index e23e7804a..28eeddd98 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -10,7 +10,7 @@ import Foundation import Articles import RSCore -public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCountProvider, DeepLinkProvider, Hashable { +public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCountProvider, Hashable { public weak var account: Account? public var topLevelFeeds: Set = Set() @@ -33,16 +33,6 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun return name ?? Folder.untitledName } - // MARK: - DeepLinkProvider - - public var deepLinkUserInfo: [AnyHashable : Any] { - return [ - DeepLinkKey.accountID.rawValue: account?.accountID ?? "", - DeepLinkKey.accountName.rawValue: account?.nameForDisplay ?? "", - DeepLinkKey.folderName.rawValue: nameForDisplay - ] - } - // MARK: - UnreadCountProvider public var unreadCount = 0 { diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 819da2fc1..d37b13a97 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -19,6 +19,8 @@ 5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */; }; 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; }; + 511B9806237DCAC90028BCAA /* UserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9805237DCAC90028BCAA /* UserInfoKey.swift */; }; + 511B9807237DCAC90028BCAA /* UserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9805237DCAC90028BCAA /* UserInfoKey.swift */; }; 511D43CF231FA62200FB1562 /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; 511D43D0231FA62500FB1562 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; }; 511D43D1231FA62800FB1562 /* SidebarKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */; }; @@ -1210,6 +1212,7 @@ 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = ""; }; 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = ""; }; 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = ""; }; + 511B9805237DCAC90028BCAA /* UserInfoKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoKey.swift; sourceTree = ""; }; 511D43EE231FBDE800FB1562 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreenPad.storyboard; sourceTree = ""; }; 511D4410231FC02D00FB1562 /* KeyboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; 512363372369155100951F16 /* RoundedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedProgressView.swift; sourceTree = ""; }; @@ -2193,8 +2196,8 @@ children = ( 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */, 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */, - 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */, 5108F6B52375E612001ABC45 /* CacheCleaner.swift */, + 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */, ); path = Data; sourceTree = ""; @@ -2424,6 +2427,7 @@ 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */, 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */, 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */, + 511B9805237DCAC90028BCAA /* UserInfoKey.swift */, 51C452AD2265102800C03939 /* Timeline */, 84702AB31FA27AE8006B8943 /* Commands */, 51934CCC231078DC006127BE /* Activity */, @@ -3881,6 +3885,7 @@ 51C45296226509D300C03939 /* OPMLExporter.swift in Sources */, 51C45291226509C800C03939 /* SmartFeed.swift in Sources */, 51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */, + 511B9807237DCAC90028BCAA /* UserInfoKey.swift in Sources */, 51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */, 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, 517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */, @@ -4074,6 +4079,7 @@ 845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */, 845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */, 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */, + 511B9806237DCAC90028BCAA /* UserInfoKey.swift in Sources */, 84C9FC7722629E1200D921D6 /* AdvancedPreferencesViewController.swift in Sources */, 849EE72120391F560082A1EA /* SharingServicePickerDelegate.swift in Sources */, 5108F6B62375E612001ABC45 /* CacheCleaner.swift in Sources */, diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift index 2c871b48e..62ecd72a2 100644 --- a/Shared/Activity/ActivityManager.swift +++ b/Shared/Activity/ActivityManager.swift @@ -9,6 +9,7 @@ import Foundation import CoreSpotlight import CoreServices +import RSCore import Account import Articles import Intents @@ -37,54 +38,15 @@ class ActivityManager { invalidateNextUnread() } - func selectingToday() { + func selecting(fetcher: ArticleFetcher) { invalidateCurrentActivities() - let title = NSLocalizedString("See articles for “Today”", comment: "Today") - selectingActivity = makeSelectingActivity(type: ActivityType.selectToday, title: title, identifier: "smartfeed.today") - donate(selectingActivity!) - } - - func selectingAllUnread() { - invalidateCurrentActivities() + selectingActivity = makeSelectFeedActivity(fetcher: fetcher) - let title = NSLocalizedString("See articles in “All Unread”", comment: "All Unread") - selectingActivity = makeSelectingActivity(type: ActivityType.selectAllUnread, title: title, identifier: "smartfeed.allUnread") - donate(selectingActivity!) - } - - func selectingStarred() { - invalidateCurrentActivities() + if let feed = fetcher as? Feed { + updateSelectingActivityFeedSearchAttributes(with: feed) + } - let title = NSLocalizedString("See articles in “Starred”", comment: "Starred") - selectingActivity = makeSelectingActivity(type: ActivityType.selectStarred, title: title, identifier: "smartfeed.starred") - donate(selectingActivity!) - } - - func selectingFolder(_ folder: Folder) { - invalidateCurrentActivities() - - let localizedText = NSLocalizedString("See articles in “%@”", comment: "See articles in Folder") - let title = NSString.localizedStringWithFormat(localizedText as NSString, folder.nameForDisplay) as String - selectingActivity = makeSelectingActivity(type: ActivityType.selectFolder, title: title, identifier: ActivityManager.identifer(for: folder)) - - let userInfo = folder.deepLinkUserInfo - selectingActivity!.userInfo = userInfo - selectingActivity!.requiredUserInfoKeys = Set(userInfo.keys.map { $0 as! String }) - donate(selectingActivity!) - } - - func selectingFeed(_ feed: Feed) { - invalidateCurrentActivities() - - let localizedText = NSLocalizedString("See articles in “%@”", comment: "See articles in Feed") - let title = NSString.localizedStringWithFormat(localizedText as NSString, feed.nameForDisplay) as String - selectingActivity = makeSelectingActivity(type: ActivityType.selectFeed, title: title, identifier: ActivityManager.identifer(for: feed)) - - let userInfo = feed.deepLinkUserInfo - selectingActivity!.userInfo = userInfo - selectingActivity!.requiredUserInfoKeys = Set(userInfo.keys.map { $0 as! String }) - updateSelectingActivityFeedSearchAttributes(with: feed) donate(selectingActivity!) } @@ -95,8 +57,17 @@ class ActivityManager { func selectingNextUnread() { guard nextUnreadActivity == nil else { return } - let title = NSLocalizedString("See first unread article", comment: "First Unread") - nextUnreadActivity = makeSelectingActivity(type: ActivityType.nextUnread, title: title, identifier: "action.nextUnread") + + nextUnreadActivity = NSUserActivity(activityType: ActivityType.nextUnread.rawValue) + nextUnreadActivity!.title = NSLocalizedString("See first unread article", comment: "First Unread") + + #if os(iOS) + nextUnreadActivity!.suggestedInvocationPhrase = nextUnreadActivity!.title + nextUnreadActivity!.isEligibleForPrediction = true + nextUnreadActivity!.persistentIdentifier = "nextUnread:" + nextUnreadActivity!.contentAttributeSet?.relatedUniqueIdentifier = "nextUnread:" + #endif + donate(nextUnreadActivity!) } @@ -105,12 +76,12 @@ class ActivityManager { nextUnreadActivity = nil } - func reading(_ article: Article?) { + func reading(fetcher: ArticleFetcher?, article: Article?) { invalidateReading() invalidateNextUnread() - guard let article = article else { return } - readingActivity = makeReadArticleActivity(article) + guard let fetcher = fetcher, let article = article else { return } + readingActivity = makeReadArticleActivity(fetcher: fetcher, article: article) #if os(iOS) updateReadArticleSearchAttributes(with: article) @@ -159,7 +130,7 @@ class ActivityManager { #endif @objc func feedIconDidBecomeAvailable(_ note: Notification) { - guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[DeepLinkKey.feedID.rawValue] as? String else { + guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else { return } @@ -180,28 +151,40 @@ class ActivityManager { private extension ActivityManager { - func makeSelectingActivity(type: ActivityType, title: String, identifier: String) -> NSUserActivity { - let activity = NSUserActivity(activityType: type.rawValue) + func makeSelectFeedActivity(fetcher: ArticleFetcher) -> NSUserActivity { + let activity = NSUserActivity(activityType: ActivityType.selectFeed.rawValue) + + let localizedText = NSLocalizedString("See articles in “%@”", comment: "See articles in Folder") + let displayName = (fetcher as? DisplayNameProvider)?.nameForDisplay ?? "" + let title = NSString.localizedStringWithFormat(localizedText as NSString, displayName) as String activity.title = title + activity.keywords = Set(makeKeywords(title)) activity.isEligibleForSearch = true + + let articleFetcherIdentifierUserInfo = fetcher.articleFetcherType?.userInfo ?? [AnyHashable: Any]() + activity.userInfo = [UserInfoKey.feedIdentifier: articleFetcherIdentifierUserInfo] + activity.requiredUserInfoKeys = Set(activity.userInfo!.keys.map { $0 as! String }) #if os(iOS) activity.suggestedInvocationPhrase = title activity.isEligibleForPrediction = true - activity.persistentIdentifier = identifier - activity.contentAttributeSet?.relatedUniqueIdentifier = identifier + activity.persistentIdentifier = fetcher.articleFetcherType?.description ?? "" + activity.contentAttributeSet?.relatedUniqueIdentifier = fetcher.articleFetcherType?.description ?? "" #endif return activity } - func makeReadArticleActivity(_ article: Article) -> NSUserActivity { + func makeReadArticleActivity(fetcher: ArticleFetcher, article: Article) -> NSUserActivity { let activity = NSUserActivity(activityType: ActivityType.readArticle.rawValue) activity.title = ArticleStringFormatter.truncatedTitle(article) - let userInfo = article.deepLinkUserInfo - activity.userInfo = userInfo - activity.requiredUserInfoKeys = Set(userInfo.keys.map { $0 as! String }) + + let articleFetcherIdentifierUserInfo = fetcher.articleFetcherType?.userInfo ?? [AnyHashable: Any]() + let articlePathUserInfo = article.pathUserInfo + activity.userInfo = [UserInfoKey.feedIdentifier: articleFetcherIdentifierUserInfo, UserInfoKey.articlePath: articlePathUserInfo] + activity.requiredUserInfoKeys = Set(activity.userInfo!.keys.map { $0 as! String }) + activity.isEligibleForHandoff = true #if os(iOS) diff --git a/Shared/Activity/ActivityType.swift b/Shared/Activity/ActivityType.swift index bce22877b..921bccda5 100644 --- a/Shared/Activity/ActivityType.swift +++ b/Shared/Activity/ActivityType.swift @@ -9,12 +9,8 @@ import Foundation enum ActivityType: String { - case selectToday = "com.ranchero.NetNewsWire.SelectToday" - case selectAllUnread = "com.ranchero.NetNewsWire.SelectAllUnread" - case selectStarred = "com.ranchero.NetNewsWire.SelectStarred" - case selectFolder = "com.ranchero.NetNewsWire.SelectFolder" - case selectFeed = "com.ranchero.NetNewsWire.SelectFeed" - case nextUnread = "com.ranchero.NetNewsWire.NextUnread" - case readArticle = "com.ranchero.NetNewsWire.ReadArticle" + case selectFeed = "SelectFeed" + case nextUnread = "NextUnread" + case readArticle = "ReadArticle" case addFeedIntent = "AddFeedIntent" } diff --git a/Shared/AppNotifications.swift b/Shared/AppNotifications.swift index 6f2e8e4bb..d7a5be8be 100644 --- a/Shared/AppNotifications.swift +++ b/Shared/AppNotifications.swift @@ -18,18 +18,3 @@ extension Notification.Name { static let WebInspectorEnabledDidChange = Notification.Name("WebInspectorEnabledDidChange") #endif } - -typealias UserInfoDictionary = [AnyHashable: Any] - -struct UserInfoKey { - - static let view = "view" - static let article = "article" - static let articles = "articles" - static let navigationKeyPressed = "navigationKeyPressed" - static let objects = "objects" - static let feed = "feed" - static let url = "url" - static let author = "author" -} - diff --git a/Shared/Data/ArticleUtilities.swift b/Shared/Data/ArticleUtilities.swift index 51c598115..72a5afe7e 100644 --- a/Shared/Data/ArticleUtilities.swift +++ b/Shared/Data/ArticleUtilities.swift @@ -94,16 +94,23 @@ extension Article { } } -// MARK: DeepLinkProvider +// MARK: Path -extension Article: DeepLinkProvider { +struct ArticlePathKey { + static let accountID = "accountID" + static let accountName = "accountName" + static let feedID = "feedID" + static let articleID = "articleID" +} - public var deepLinkUserInfo: [AnyHashable : Any] { +extension Article { + + public var pathUserInfo: [AnyHashable : Any] { return [ - DeepLinkKey.accountID.rawValue: accountID, - DeepLinkKey.accountName.rawValue: account?.nameForDisplay ?? "", - DeepLinkKey.feedID.rawValue: feedID, - DeepLinkKey.articleID.rawValue: articleID + ArticlePathKey.accountID: accountID, + ArticlePathKey.accountName: account?.nameForDisplay ?? "", + ArticlePathKey.feedID: feedID, + ArticlePathKey.articleID: articleID ] } diff --git a/Shared/SmartFeeds/SearchFeedDelegate.swift b/Shared/SmartFeeds/SearchFeedDelegate.swift index 413d0bc31..6b1cdaca4 100644 --- a/Shared/SmartFeeds/SearchFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchFeedDelegate.swift @@ -13,6 +13,10 @@ import Articles struct SearchFeedDelegate: SmartFeedDelegate { + var articleFetcherType: ArticleFetcherType? { + return ArticleFetcherType.smartFeed(String(describing: SearchFeedDelegate.self)) + } + var nameForDisplay: String { return nameForDisplayPrefix + searchString } diff --git a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift index 84df490ac..3df0bc222 100644 --- a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift @@ -13,6 +13,10 @@ import Articles struct SearchTimelineFeedDelegate: SmartFeedDelegate { + var articleFetcherType: ArticleFetcherType? { + return ArticleFetcherType.smartFeed(String(describing: SearchTimelineFeedDelegate.self)) + } + var nameForDisplay: String { return nameForDisplayPrefix + searchString } diff --git a/Shared/SmartFeeds/SmartFeed.swift b/Shared/SmartFeeds/SmartFeed.swift index 4bba4d7a1..aef721a58 100644 --- a/Shared/SmartFeeds/SmartFeed.swift +++ b/Shared/SmartFeeds/SmartFeed.swift @@ -71,6 +71,10 @@ final class SmartFeed: PseudoFeed { } extension SmartFeed: ArticleFetcher { + + var articleFetcherType: ArticleFetcherType? { + delegate.articleFetcherType + } func fetchArticles() -> Set
{ return delegate.fetchArticles() diff --git a/Shared/SmartFeeds/SmartFeedsController.swift b/Shared/SmartFeeds/SmartFeedsController.swift index ad67c1120..37a424a17 100644 --- a/Shared/SmartFeeds/SmartFeedsController.swift +++ b/Shared/SmartFeeds/SmartFeedsController.swift @@ -20,7 +20,20 @@ final class SmartFeedsController: DisplayNameProvider { let starredFeed = SmartFeed(delegate: StarredFeedDelegate()) private init() { - self.smartFeeds = [todayFeed, unreadFeed, starredFeed] } + + func find(by identifier: String) -> PseudoFeed? { + switch identifier { + case String(describing: TodayFeedDelegate.self): + return todayFeed + case String(describing: UnreadFeed.self): + return unreadFeed + case String(describing: StarredFeedDelegate.self): + return starredFeed + default: + return nil + } + } + } diff --git a/Shared/SmartFeeds/StarredFeedDelegate.swift b/Shared/SmartFeeds/StarredFeedDelegate.swift index 796f13b7a..df4ad368b 100644 --- a/Shared/SmartFeeds/StarredFeedDelegate.swift +++ b/Shared/SmartFeeds/StarredFeedDelegate.swift @@ -15,6 +15,10 @@ import Account struct StarredFeedDelegate: SmartFeedDelegate { + var articleFetcherType: ArticleFetcherType? { + return ArticleFetcherType.smartFeed(String(describing: StarredFeedDelegate.self)) + } + let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title") let fetchType: FetchType = .starred var smallIcon: IconImage? = AppAssets.starredFeedImage diff --git a/Shared/SmartFeeds/TodayFeedDelegate.swift b/Shared/SmartFeeds/TodayFeedDelegate.swift index 2d350c577..37f67723f 100644 --- a/Shared/SmartFeeds/TodayFeedDelegate.swift +++ b/Shared/SmartFeeds/TodayFeedDelegate.swift @@ -13,6 +13,10 @@ import Account struct TodayFeedDelegate: SmartFeedDelegate { + var articleFetcherType: ArticleFetcherType? { + return ArticleFetcherType.smartFeed(String(describing: TodayFeedDelegate.self)) + } + let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title") let fetchType = FetchType.today var smallIcon: IconImage? = AppAssets.todayFeedImage diff --git a/Shared/SmartFeeds/UnreadFeed.swift b/Shared/SmartFeeds/UnreadFeed.swift index 20c3926df..8955597a2 100644 --- a/Shared/SmartFeeds/UnreadFeed.swift +++ b/Shared/SmartFeeds/UnreadFeed.swift @@ -52,6 +52,10 @@ final class UnreadFeed: PseudoFeed { } extension UnreadFeed: ArticleFetcher { + + var articleFetcherType: ArticleFetcherType? { + return ArticleFetcherType.smartFeed(String(describing: UnreadFeed.self)) + } func fetchArticles() -> Set
{ return fetchUnreadArticles() diff --git a/Shared/UserInfoKey.swift b/Shared/UserInfoKey.swift new file mode 100644 index 000000000..dfdce4e87 --- /dev/null +++ b/Shared/UserInfoKey.swift @@ -0,0 +1,26 @@ +// +// UserInfoKey.swift +// NetNewsWire +// +// Created by Maurice Parker on 11/14/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation + +typealias UserInfoDictionary = [AnyHashable: Any] + +struct UserInfoKey { + + static let view = "view" + static let article = "article" + static let articles = "articles" + static let navigationKeyPressed = "navigationKeyPressed" + static let objects = "objects" + static let feed = "feed" + static let url = "url" + static let author = "author" + static let articlePath = "articlePath" + static let feedIdentifier = "feedIdentifier" + +} diff --git a/Shared/UserNotifications/UserNotificationManager.swift b/Shared/UserNotifications/UserNotificationManager.swift index fbf3608f1..2b42228ec 100644 --- a/Shared/UserNotifications/UserNotificationManager.swift +++ b/Shared/UserNotifications/UserNotificationManager.swift @@ -53,7 +53,7 @@ private extension UserNotificationManager { } content.sound = UNNotificationSound.default - content.userInfo = article.deepLinkUserInfo + content.userInfo = [UserInfoKey.articlePath: article.pathUserInfo] let request = UNNotificationRequest.init(identifier: "articleID:\(article.articleID)", content: content, trigger: nil) UNUserNotificationCenter.current().add(request) diff --git a/iOS/Resources/Info.plist b/iOS/Resources/Info.plist index dfcacffa8..9eab84870 100644 --- a/iOS/Resources/Info.plist +++ b/iOS/Resources/Info.plist @@ -61,13 +61,9 @@ NSUserActivityTypes AddFeedIntent - com.ranchero.NetNewsWire.NextUnread - com.ranchero.NetNewsWire.ReadArticle - com.ranchero.NetNewsWire.SelectAllUnread - com.ranchero.NetNewsWire.SelectFeed - com.ranchero.NetNewsWire.SelectFolder - com.ranchero.NetNewsWire.SelectStarred - com.ranchero.NetNewsWire.SelectToday + NextUnread + ReadArticle + SelectFeed UIApplicationSceneManifest diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index d84c0636a..2d229b9a8 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -332,14 +332,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { guard let activityType = ActivityType(rawValue: activity.activityType) else { return } switch activityType { - case .selectToday: - handleSelectToday() - case .selectAllUnread: - handleSelectAllUnread() - case .selectStarred: - handleSelectStarred() - case .selectFolder: - handleSelectFolder(activity.userInfo) case .selectFeed: handleSelectFeed(activity.userInfo) case .nextUnread: @@ -543,7 +535,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { if let ip = indexPath, let node = nodeFor(ip), let fetcher = node.representedObject as? ArticleFetcher { timelineFetcher = fetcher - updateSelectingActivity(with: node) + activityManager.selecting(fetcher: fetcher) installTimelineControllerIfNecessary(animated: animated) } else { timelineFetcher = nil @@ -590,7 +582,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { stopArticleExtractor() currentArticle = article - activityManager.reading(currentArticle) + activityManager.reading(fetcher: timelineFetcher, article: article) if article == nil { if rootSplitViewController.isCollapsed { @@ -1630,108 +1622,84 @@ private extension SceneCoordinator { // MARK: NSUserActivity - func updateSelectingActivity(with node: Node) { - switch true { - case node.representedObject === SmartFeedsController.shared.todayFeed: - activityManager.selectingToday() - case node.representedObject === SmartFeedsController.shared.unreadFeed: - activityManager.selectingAllUnread() - case node.representedObject === SmartFeedsController.shared.starredFeed: - activityManager.selectingStarred() - case node.representedObject is Folder: - activityManager.selectingFolder(node.representedObject as! Folder) - case node.representedObject is Feed: - activityManager.selectingFeed(node.representedObject as! Feed) - default: - break - } - } - - func handleSelectToday() { - if let indexPath = indexPathFor(SmartFeedsController.shared.todayFeed) { - selectFeed(indexPath, animated: false) - } - } - - func handleSelectAllUnread() { - if let indexPath = indexPathFor(SmartFeedsController.shared.unreadFeed) { - selectFeed(indexPath, animated: false) - } - } - - func handleSelectStarred() { - if let indexPath = indexPathFor(SmartFeedsController.shared.starredFeed) { - selectFeed(indexPath, animated: false) - } - } - - func handleSelectFolder(_ userInfo: [AnyHashable : Any]?) { - guard let accountNode = findAccountNode(userInfo), let folderNode = findFolderNode(userInfo, beginningAt: accountNode) else { - return - } - if let indexPath = indexPathFor(folderNode) { - selectFeed(indexPath, animated: false) - } - } - func handleSelectFeed(_ userInfo: [AnyHashable : Any]?) { - guard let accountNode = findAccountNode(userInfo), let feedNode = findFeedNode(userInfo, beginningAt: accountNode) else { - return - } - if let feed = feedNode.representedObject as? Feed { - discloseFeed(feed, animated: false) - } - } - - func handleReadArticle(_ userInfo: [AnyHashable : Any]?) { - guard let accountNode = findAccountNode(userInfo), let feedNode = findFeedNode(userInfo, beginningAt: accountNode) else { - return + guard let userInfo = userInfo, + let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : Any], + let articleFetcherType = ArticleFetcherType(userInfo: feedIdentifierUserInfo) else { + return } + + switch articleFetcherType { - discloseFeed(feedNode.representedObject as! Feed, animated: false) { + case .smartFeed(let identifier): + guard let smartFeed = SmartFeedsController.shared.find(by: identifier) else { return } + if let indexPath = indexPathFor(smartFeed) { + selectFeed(indexPath, animated: false) + } - guard let articleID = userInfo?[DeepLinkKey.articleID.rawValue] as? String else { return } - if let article = self.articles.first(where: { $0.articleID == articleID }) { - self.selectArticle(article) + case .script: + break + + case .folder(let accountID, let folderName): + guard let accountNode = findAccountNode(accountID: accountID), let folderNode = findFolderNode(folderName: folderName, beginningAt: accountNode) else { + return + } + if let indexPath = indexPathFor(folderNode) { + selectFeed(indexPath, animated: false) + } + + case .feed(let accountID, let feedID): + guard let accountNode = findAccountNode(accountID: accountID), let feedNode = findFeedNode(feedID: feedID, beginningAt: accountNode) else { + return + } + if let feed = feedNode.representedObject as? Feed { + discloseFeed(feed, animated: false) } } } - func findAccountNode(_ userInfo: [AnyHashable : Any]?) -> Node? { - guard let accountID = userInfo?[DeepLinkKey.accountID.rawValue] as? String else { - return nil + func handleReadArticle(_ userInfo: [AnyHashable : Any]?) { + guard let userInfo = userInfo, + let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any], + let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String, + let accountName = articlePathUserInfo[ArticlePathKey.accountName] as? String, + let feedID = articlePathUserInfo[ArticlePathKey.feedID] as? String, + let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else { + return + } + + guard let accountNode = findAccountNode(accountID: accountID, accountName: accountName), let feedNode = findFeedNode(feedID: feedID, beginningAt: accountNode) else { + return } + discloseFeed(feedNode.representedObject as! Feed, animated: false) { + if let article = self.articles.first(where: { $0.articleID == articleID }) { + self.selectArticle(article) + } + } + } + + func findAccountNode(accountID: String, accountName: String? = nil) -> Node? { if let node = treeController.rootNode.descendantNode(where: { ($0.representedObject as? Account)?.accountID == accountID }) { return node } - guard let accountName = userInfo?[DeepLinkKey.accountName.rawValue] as? String else { - return nil - } - - if let node = treeController.rootNode.descendantNode(where: { ($0.representedObject as? Account)?.nameForDisplay == accountName }) { + if let accountName = accountName, let node = treeController.rootNode.descendantNode(where: { ($0.representedObject as? Account)?.nameForDisplay == accountName }) { return node } return nil } - func findFolderNode(_ userInfo: [AnyHashable : Any]?, beginningAt startingNode: Node) -> Node? { - guard let folderName = userInfo?[DeepLinkKey.folderName.rawValue] as? String else { - return nil - } + func findFolderNode(folderName: String, beginningAt startingNode: Node) -> Node? { if let node = startingNode.descendantNode(where: { ($0.representedObject as? Folder)?.nameForDisplay == folderName }) { return node } return nil } - func findFeedNode(_ userInfo: [AnyHashable : Any]?, beginningAt startingNode: Node) -> Node? { - guard let feedID = userInfo?[DeepLinkKey.feedID.rawValue] as? String else { - return nil - } + func findFeedNode(feedID: String, beginningAt startingNode: Node) -> Node? { if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) { return node } From 15e62a07507a955d5041866b0c4c2831b788b2e6 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 14 Nov 2019 15:35:19 -0600 Subject: [PATCH 198/237] Update Mac app to work with latest ActivityManager. --- Mac/MainWindow/MainWindowController.swift | 12 +++++++----- Mac/MainWindow/Sidebar/SidebarViewController.swift | 6 +++--- .../Timeline/TimelineViewController.swift | 2 +- Mac/Resources/Info.plist | 2 +- Shared/Activity/ActivityManager.swift | 14 +++++++++----- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 8f385dc12..f7792d310 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -117,14 +117,16 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { func handle(_ response: UNNotificationResponse) { let userInfo = response.notification.request.content.userInfo - sidebarViewController?.deepLinkRevealAndSelect(for: userInfo) - currentTimelineViewController?.goToDeepLink(for: userInfo) + guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any] else { return } + sidebarViewController?.deepLinkRevealAndSelect(for: articlePathUserInfo) + currentTimelineViewController?.goToDeepLink(for: articlePathUserInfo) } func handle(_ activity: NSUserActivity) { guard let userInfo = activity.userInfo else { return } - sidebarViewController?.deepLinkRevealAndSelect(for: userInfo) - currentTimelineViewController?.goToDeepLink(for: userInfo) + guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any] else { return } + sidebarViewController?.deepLinkRevealAndSelect(for: articlePathUserInfo) + currentTimelineViewController?.goToDeepLink(for: articlePathUserInfo) } // MARK: - Notifications @@ -479,7 +481,7 @@ extension MainWindowController: TimelineContainerViewControllerDelegate { let detailState: DetailState if let articles = articles { if articles.count == 1 { - activityManager.reading(articles.first!) + activityManager.reading(fetcher: nil, article: articles.first) if articles.first?.feed?.isArticleExtractorAlwaysOn ?? false { detailState = .loading startArticleExtractorForCurrentLink() diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index 02dd187ce..f1d19437f 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -484,7 +484,7 @@ private extension SidebarViewController { } func findAccountNode(_ userInfo: [AnyHashable : Any]?) -> Node? { - guard let accountID = userInfo?[DeepLinkKey.accountID.rawValue] as? String else { + guard let accountID = userInfo?[ArticlePathKey.accountID] as? String else { return nil } @@ -492,7 +492,7 @@ private extension SidebarViewController { return node } - guard let accountName = userInfo?[DeepLinkKey.accountName.rawValue] as? String else { + guard let accountName = userInfo?[ArticlePathKey.accountName] as? String else { return nil } @@ -504,7 +504,7 @@ private extension SidebarViewController { } func findFeedNode(_ userInfo: [AnyHashable : Any]?, beginningAt startingNode: Node) -> Node? { - guard let feedID = userInfo?[DeepLinkKey.feedID.rawValue] as? String else { + guard let feedID = userInfo?[ArticlePathKey.feedID] as? String else { return nil } if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) { diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 9f4bba8b1..f4ab509e8 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -388,7 +388,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr // MARK: - Navigation func goToDeepLink(for userInfo: [AnyHashable : Any]) { - guard let articleID = userInfo[DeepLinkKey.articleID.rawValue] as? String else { return } + guard let articleID = userInfo[ArticlePathKey.articleID] as? String else { return } guard let ix = articles.firstIndex(where: { $0.articleID == articleID }) else { return } NSCursor.setHiddenUntilMouseMoves(true) diff --git a/Mac/Resources/Info.plist b/Mac/Resources/Info.plist index d761dde8e..29185b54b 100644 --- a/Mac/Resources/Info.plist +++ b/Mac/Resources/Info.plist @@ -45,7 +45,7 @@ NSUserActivityTypes - com.ranchero.NetNewsWire.ReadArticle + ReadArticle NSAppleEventsUsageDescription NetNewsWire communicates with other apps on your Mac when you choose to share an article. diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift index 62ecd72a2..276dc46e8 100644 --- a/Shared/Activity/ActivityManager.swift +++ b/Shared/Activity/ActivityManager.swift @@ -80,7 +80,7 @@ class ActivityManager { invalidateReading() invalidateNextUnread() - guard let fetcher = fetcher, let article = article else { return } + guard let article = article else { return } readingActivity = makeReadArticleActivity(fetcher: fetcher, article: article) #if os(iOS) @@ -176,13 +176,17 @@ private extension ActivityManager { return activity } - func makeReadArticleActivity(fetcher: ArticleFetcher, article: Article) -> NSUserActivity { + func makeReadArticleActivity(fetcher: ArticleFetcher?, article: Article) -> NSUserActivity { let activity = NSUserActivity(activityType: ActivityType.readArticle.rawValue) activity.title = ArticleStringFormatter.truncatedTitle(article) - let articleFetcherIdentifierUserInfo = fetcher.articleFetcherType?.userInfo ?? [AnyHashable: Any]() - let articlePathUserInfo = article.pathUserInfo - activity.userInfo = [UserInfoKey.feedIdentifier: articleFetcherIdentifierUserInfo, UserInfoKey.articlePath: articlePathUserInfo] + if let fetcher = fetcher { + let articleFetcherIdentifierUserInfo = fetcher.articleFetcherType?.userInfo ?? [AnyHashable: Any]() + let articlePathUserInfo = article.pathUserInfo + activity.userInfo = [UserInfoKey.feedIdentifier: articleFetcherIdentifierUserInfo, UserInfoKey.articlePath: articlePathUserInfo] + } else { + activity.userInfo = [UserInfoKey.articlePath: article.pathUserInfo] + } activity.requiredUserInfoKeys = Set(activity.userInfo!.keys.map { $0 as! String }) activity.isEligibleForHandoff = true From f5cd5d70675cb54eb1669b0ce187689fcb2412b7 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 14 Nov 2019 15:39:11 -0600 Subject: [PATCH 199/237] Reload expanded node so that the disclosure arrow is correct. --- iOS/MasterFeed/MasterFeedViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 334ebbeff..df76e98d4 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -528,6 +528,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } coordinator.expand(parent) + reloadNode(parent) self.applyChanges(animate: true, adjustScroll: true) { [weak self] in if let indexPath = self?.dataSource.indexPath(for: node) { From b317a99594f834b6c1fa1800a8abe94f18bb02d5 Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Fri, 15 Nov 2019 09:32:02 +1100 Subject: [PATCH 200/237] Fix failing Feedly tests. --- .../Account/AccountTests/Feedly/FeedlyOperationTests.swift | 1 + .../AccountTests/Feedly/FeedlySyncAllOperationTests.swift | 4 +++- .../Account/Feedly/Operations/FeedlySyncAllOperation.swift | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Frameworks/Account/AccountTests/Feedly/FeedlyOperationTests.swift b/Frameworks/Account/AccountTests/Feedly/FeedlyOperationTests.swift index dc880597d..12992d748 100644 --- a/Frameworks/Account/AccountTests/Feedly/FeedlyOperationTests.swift +++ b/Frameworks/Account/AccountTests/Feedly/FeedlyOperationTests.swift @@ -102,6 +102,7 @@ class FeedlyOperationTests: XCTestCase { func testOperationCancellationFlags() { let testOperation = TestOperation() testOperation.didCallMainExpectation = expectation(description: "Did Call Main") + testOperation.didCallMainExpectation?.isInverted = true let completionExpectation = expectation(description: "Operation Completed") testOperation.completionBlock = { diff --git a/Frameworks/Account/AccountTests/Feedly/FeedlySyncAllOperationTests.swift b/Frameworks/Account/AccountTests/Feedly/FeedlySyncAllOperationTests.swift index cd1a988d3..5c7869439 100644 --- a/Frameworks/Account/AccountTests/Feedly/FeedlySyncAllOperationTests.swift +++ b/Frameworks/Account/AccountTests/Feedly/FeedlySyncAllOperationTests.swift @@ -67,11 +67,13 @@ class FeedlySyncAllOperationTests: XCTestCase { } let syncCompletionExpectation = expectation(description: "Did Finish Sync") + syncCompletionExpectation.isInverted = true syncAll.syncCompletionHandler = { result in switch result { case .success: - XCTFail("Expected failure.") + XCTFail("Sync operation was cancelled, not successful.") case .failure: + XCTFail("Sync operation should cancel silently.") break } syncCompletionExpectation.fulfill() diff --git a/Frameworks/Account/Feedly/Operations/FeedlySyncAllOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlySyncAllOperation.swift index b93031677..ff6a1df96 100644 --- a/Frameworks/Account/Feedly/Operations/FeedlySyncAllOperation.swift +++ b/Frameworks/Account/Feedly/Operations/FeedlySyncAllOperation.swift @@ -97,6 +97,9 @@ final class FeedlySyncAllOperation: FeedlyOperation { super.cancel() didFinish() + + // Operation should silently cancel. + syncCompletionHandler = nil } override func main() { From bec80922ce1f9994ee1de0a5b286433f282a597e Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Mon, 11 Nov 2019 18:42:31 +1100 Subject: [PATCH 201/237] Enables accounts and their delegates to prepare to be deleted. Provides a variation of the Account Inspector on iOS for Feedly. --- Frameworks/Account/Account.swift | 4 +++ Frameworks/Account/AccountDelegate.swift | 2 ++ Frameworks/Account/AccountManager.swift | 2 ++ .../Feedbin/FeedbinAccountDelegate.swift | 3 ++ .../Feedly/FeedlyAccountDelegate.swift | 4 +++ .../LocalAccount/LocalAccountDelegate.swift | 3 ++ .../ReaderAPI/ReaderAPIAccountDelegate.swift | 3 ++ .../AccountsDetailViewController.swift | 17 ++++++++-- .../AccountInspectorViewController.swift | 34 +++++++++++++++---- 9 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 597fd920e..64972e775 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -399,6 +399,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, opmlFile.save() } + public func prepareForDeletion() { + delegate.accountWillBeDeleted(self) + } + func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) { var feedsToAdd = Set() diff --git a/Frameworks/Account/AccountDelegate.swift b/Frameworks/Account/AccountDelegate.swift index de13bf9cb..d03b7def7 100644 --- a/Frameworks/Account/AccountDelegate.swift +++ b/Frameworks/Account/AccountDelegate.swift @@ -46,6 +46,8 @@ protocol AccountDelegate { // Called at the end of account’s init method. func accountDidInitialize(_ account: Account) + + func accountWillBeDeleted(_ account: Account) static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result) -> Void) diff --git a/Frameworks/Account/AccountManager.swift b/Frameworks/Account/AccountManager.swift index 385b5e389..1c29e6495 100644 --- a/Frameworks/Account/AccountManager.swift +++ b/Frameworks/Account/AccountManager.swift @@ -138,6 +138,8 @@ public final class AccountManager: UnreadCountProvider { return } + account.prepareForDeletion() + accountsDictionary.removeValue(forKey: account.accountID) account.isDeleted = true diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index ea3027a4f..0c741921f 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -541,6 +541,9 @@ final class FeedbinAccountDelegate: AccountDelegate { credentials = try? account.retrieveCredentials(type: .basic) } + func accountWillBeDeleted(_ account: Account) { + } + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: @escaping (Result) -> Void) { let caller = FeedbinAPICaller(transport: transport) diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index fbec23b02..d80e7699f 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -486,6 +486,10 @@ final class FeedlyAccountDelegate: AccountDelegate { operationQueue.addOperation(refreshAccessToken) } + func accountWillBeDeleted(_ account: Account) { + + } + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result) -> Void) { assertionFailure("An `account` instance should enqueue an \(FeedlyRefreshAccessTokenOperation.self) instead.") completion(.success(credentials)) diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 25a62e74c..fb8412438 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -195,6 +195,9 @@ final class LocalAccountDelegate: AccountDelegate { func accountDidInitialize(_ account: Account) { } + + func accountWillBeDeleted(_ account: Account) { + } static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: (Result) -> Void) { return completion(.success(nil)) diff --git a/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 4de3ff830..b396d0537 100644 --- a/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -418,6 +418,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate { credentials = try? account.retrieveCredentials(type: .readerAPIKey) } + func accountWillBeDeleted(_ account: Account) { + } + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result) -> Void) { guard let endpoint = endpoint else { completion(.failure(TransportError.noURL)) diff --git a/Mac/Preferences/Accounts/AccountsDetailViewController.swift b/Mac/Preferences/Accounts/AccountsDetailViewController.swift index 902293e39..4a04625f7 100644 --- a/Mac/Preferences/Accounts/AccountsDetailViewController.swift +++ b/Mac/Preferences/Accounts/AccountsDetailViewController.swift @@ -28,6 +28,19 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate super.init(coder: coder) } + private var hidesCredentialsButton: Bool { + guard let account = account else { + return true + } + switch account.type { + case .onMyMac, + .feedly: + return true + default: + return false + } + } + override func viewDidLoad() { super.viewDidLoad() @@ -35,7 +48,7 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate typeLabel.stringValue = account?.defaultName ?? "" nameTextField.stringValue = account?.name ?? "" activeButton.state = account?.isActive ?? false ? .on : .off - credentialsButton.isHidden = account?.type ?? .onMyMac == .onMyMac + credentialsButton.isHidden = hidesCredentialsButton } func controlTextDidEndEditing(_ obj: Notification) { @@ -66,8 +79,6 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate accountsFreshRSSWindowController.account = account accountsFreshRSSWindowController.runSheetOnWindow(self.view.window!) accountsWindowController = accountsFreshRSSWindowController - case .feedly: - assertionFailure("Implement feedly logout window controller") break default: break diff --git a/iOS/Inspector/AccountInspectorViewController.swift b/iOS/Inspector/AccountInspectorViewController.swift index b4e3b4d7a..09d41e12c 100644 --- a/iOS/Inspector/AccountInspectorViewController.swift +++ b/iOS/Inspector/AccountInspectorViewController.swift @@ -64,10 +64,20 @@ class AccountInspectorViewController: UITableViewController { } @IBAction func deleteAccount(_ sender: Any) { - let title = NSLocalizedString("Delete Account", comment: "Delete Account") - let message = NSLocalizedString("Are you sure you want to delete this account? This can not be undone.", comment: "Delete Account") - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + guard let account = account else { + return + } + let title = NSLocalizedString("Delete Account", comment: "Delete Account") + let message: String = { + switch account.type { + case .feedly: + return NSLocalizedString("Are you sure you want to delete this account? NetNewsWire will no longer be able to access articles and feeds unless the account is added again.", comment: "Log Out and Delete Account") + default: + return NSLocalizedString("Are you sure you want to delete this account? This can not be undone.", comment: "Delete Account") + } + }() + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel") let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) alertController.addAction(cancelAction) @@ -86,19 +96,31 @@ class AccountInspectorViewController: UITableViewController { present(alertController, animated: true) } - } // MARK: Table View extension AccountInspectorViewController { + + var hidesCredentialsSection: Bool { + guard let account = account else { + return true + } + switch account.type { + case .onMyMac, + .feedly: + return true + default: + return false + } + } override func numberOfSections(in tableView: UITableView) -> Int { guard let account = account else { return 0 } if account == AccountManager.shared.defaultAccount { return 1 - } else if account.type == .onMyMac { + } else if hidesCredentialsSection { return 2 } else { return super.numberOfSections(in: tableView) @@ -124,7 +146,7 @@ extension AccountInspectorViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell - if indexPath.section == 1, let account = account, account.type == .onMyMac { + if indexPath.section == 1, hidesCredentialsSection { cell = super.tableView(tableView, cellForRowAt: IndexPath(row: 0, section: 2)) } else { cell = super.tableView(tableView, cellForRowAt: indexPath) From 06bd5b3a6f450794792cf143836a7414816089aa Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 14 Nov 2019 20:11:41 -0600 Subject: [PATCH 202/237] Renamed Feed and related classes/instances to WebFeed --- Frameworks/Account/Account.swift | 302 +++++++++--------- .../Account/Account.xcodeproj/project.pbxproj | 24 +- Frameworks/Account/AccountDelegate.swift | 12 +- Frameworks/Account/AccountManager.swift | 4 +- ...AccountFeedbinFolderContentsSyncTest.swift | 12 +- .../Feedbin/AccountFeedbinSyncTest.swift | 8 +- ...dsForCollectionFoldersOperationTests.swift | 16 +- ...orCollectionsAsFoldersOperationTests.swift | 4 +- .../Feedly/FeedlyTestSupport.swift | 6 +- Frameworks/Account/ArticleFetcher.swift | 10 +- Frameworks/Account/ArticleFetcherType.swift | 14 +- Frameworks/Account/Container.swift | 58 ++-- Frameworks/Account/DataExtensions.swift | 20 +- .../Account/Feedbin/FeedbinAPICaller.swift | 4 +- .../Feedbin/FeedbinAccountDelegate.swift | 126 ++++---- .../Feedly/FeedlyAccountDelegate.swift | 46 +-- .../Feedly/FeedlyAccountDelegateError.swift | 4 +- .../Account/Feedly/FeedlyAddFeedRequest.swift | 12 +- ...teFeedsForCollectionFoldersOperation.swift | 18 +- ...UpdateAccountFeedsWithItemsOperation.swift | 6 +- Frameworks/Account/Folder.swift | 34 +- .../LocalAccount/LocalAccountDelegate.swift | 30 +- .../LocalAccount/LocalAccountRefresher.swift | 8 +- Frameworks/Account/OPMLFile.swift | 2 +- .../ReaderAPI/ReaderAPIAccountDelegate.swift | 114 +++---- .../Account/ReaderAPI/ReaderAPICaller.swift | 4 +- .../Account/{Feed.swift => WebFeed.swift} | 30 +- ...edMetadata.swift => WebFeedMetadata.swift} | 22 +- ...taFile.swift => WebFeedMetadataFile.swift} | 22 +- Frameworks/Articles/Article.swift | 12 +- .../ArticlesDatabase/ArticlesDatabase.swift | 68 ++-- .../ArticlesDatabase/ArticlesTable.swift | 124 +++---- .../ArticlesDatabase/DatabaseArticle.swift | 2 +- .../Extensions/Article+Database.swift | 14 +- .../Extensions/ParsedArticle+Database.swift | 2 +- Mac/AppDelegate.swift | 12 +- Mac/Inspector/Inspector.storyboard | 2 +- ...t => WebFeedInspectorViewController.swift} | 14 +- .../AddFeed/AddFeedController.swift | 6 +- Mac/MainWindow/MainWindowController.swift | 4 +- Mac/MainWindow/Sidebar/PasteboardFolder.swift | 6 +- ...oardFeed.swift => PasteboardWebFeed.swift} | 98 +++--- .../Sidebar/SidebarOutlineDataSource.swift | 98 +++--- ...idebarViewController+ContextualMenus.swift | 26 +- .../Sidebar/SidebarViewController.swift | 32 +- .../Timeline/ArticlePasteboardWriter.swift | 8 +- ...melineViewController+ContextualMenus.swift | 10 +- .../Timeline/TimelineViewController.swift | 34 +- Mac/Scriptability/Account+Scriptability.swift | 14 +- Mac/Scriptability/Feed+Scriptability.swift | 36 +-- Mac/Scriptability/Folder+Scriptability.swift | 4 +- .../NSApplication+Scriptability.swift | 14 +- NetNewsWire.xcodeproj/project.pbxproj | 84 ++--- Shared/Activity/ActivityManager.swift | 40 +-- .../Article Rendering/ArticleRenderer.swift | 12 +- Shared/Commands/DeleteCommand.swift | 28 +- Shared/Commands/SendToMarsEditCommand.swift | 2 +- Shared/Commands/SendToMicroBlogCommand.swift | 4 +- Shared/Data/ArticleUtilities.swift | 24 +- Shared/Data/SmallIconProvider.swift | 2 +- Shared/Favicons/FaviconDownloader.swift | 20 +- Shared/Favicons/FaviconGenerator.swift | 8 +- ...ader.swift => WebFeedIconDownloader.swift} | 32 +- Shared/Timeline/ArticleSorter.swift | 4 +- ...ft => WebFeedTreeControllerDelegate.swift} | 14 +- Shared/UserInfoKey.swift | 2 +- .../UserNotificationManager.swift | 8 +- iOS/Add/Add.storyboard | 12 +- iOS/Add/AddContainerViewController.swift | 6 +- ...r.swift => AddWebFeedViewController.swift} | 12 +- iOS/AppDelegate.swift | 4 +- iOS/Inspector/Inspector.storyboard | 4 +- ...t => WebFeedInspectorViewController.swift} | 42 +-- ...er.swift => AddWebFeedIntentHandler.swift} | 36 +-- .../Base.lproj/Intents.intentdefinition | 12 +- iOS/IntentsExtension/Info.plist | 2 +- iOS/IntentsExtension/IntentHandler.swift | 4 +- iOS/KeyboardManager.swift | 2 +- iOS/MasterFeed/MasterFeedDataSource.swift | 30 +- iOS/MasterFeed/MasterFeedViewController.swift | 58 ++-- .../MasterTimelineViewController.swift | 34 +- iOS/Resources/Info.plist | 2 +- iOS/SceneCoordinator.swift | 50 +-- .../TimelinePreviewTableViewController.swift | 2 +- iOS/ShareExtension/ShareViewController.swift | 4 +- 85 files changed, 1106 insertions(+), 1106 deletions(-) rename Frameworks/Account/{Feed.swift => WebFeed.swift} (88%) rename Frameworks/Account/{FeedMetadata.swift => WebFeedMetadata.swift} (83%) rename Frameworks/Account/{FeedMetadataFile.swift => WebFeedMetadataFile.swift} (74%) rename Mac/Inspector/{FeedInspectorViewController.swift => WebFeedInspectorViewController.swift} (88%) rename Mac/MainWindow/Sidebar/{PasteboardFeed.swift => PasteboardWebFeed.swift} (50%) rename Shared/Images/{FeedIconDownloader.swift => WebFeedIconDownloader.swift} (84%) rename Shared/Tree/{FeedTreeControllerDelegate.swift => WebFeedTreeControllerDelegate.swift} (88%) rename iOS/Add/{AddFeedViewController.swift => AddWebFeedViewController.swift} (90%) rename iOS/Inspector/{FeedInspectorViewController.swift => WebFeedInspectorViewController.swift} (61%) rename iOS/Intents/{AddFeedIntentHandler.swift => AddWebFeedIntentHandler.swift} (61%) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 597fd920e..0525a5e0e 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -29,7 +29,7 @@ public extension Notification.Name { static let AccountDidDownloadArticles = Notification.Name(rawValue: "AccountDidDownloadArticles") static let AccountStateDidChange = Notification.Name(rawValue: "AccountStateDidChange") static let StatusesDidChange = Notification.Name(rawValue: "StatusesDidChange") - static let FeedMetadataDidChange = Notification.Name(rawValue: "FeedMetadataDidChange") + static let WebFeedMetadataDidChange = Notification.Name(rawValue: "WebFeedMetadataDidChange") } public enum AccountType: Int { @@ -48,7 +48,7 @@ public enum FetchType { case unread case today case unreadForFolder(Folder) - case feed(Feed) + case webFeed(WebFeed) case articleIDs(Set) case search(String) case searchWithArticleIDs(String, Set) @@ -62,7 +62,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, public static let updatedArticles = "updatedArticles" // AccountDidDownloadArticles public static let statuses = "statuses" // StatusesDidChange public static let articles = "articles" // StatusesDidChange - public static let feeds = "feeds" // AccountDidDownloadArticles, StatusesDidChange + public static let webFeeds = "webFeeds" // AccountDidDownloadArticles, StatusesDidChange } public static let defaultLocalAccountName: String = { @@ -126,15 +126,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } } - public var topLevelFeeds = Set() + public var topLevelWebFeeds = Set() public var folders: Set? = Set() - private var feedDictionaryNeedsUpdate = true - private var _idToFeedDictionary = [String: Feed]() - var idToFeedDictionary: [String: Feed] { - if feedDictionaryNeedsUpdate { - rebuildFeedDictionaries() + private var webFeedDictionaryNeedsUpdate = true + private var _idToWebFeedDictionary = [String: WebFeed]() + var idToWebFeedDictionary: [String: WebFeed] { + if webFeedDictionaryNeedsUpdate { + rebuildWebFeedDictionaries() } - return _idToFeedDictionary + return _idToWebFeedDictionary } var username: String? { @@ -169,8 +169,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, private var unreadCounts = [String: Int]() // [feedID: Int] - private var _flattenedFeeds = Set() - private var flattenedFeedsNeedUpdate = true + private var _flattenedWebFeeds = Set() + private var flattenedWebFeedsNeedUpdate = true private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self) private lazy var metadataFile = AccountMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("Settings.plist"), account: self) @@ -180,9 +180,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } } - private lazy var feedMetadataFile = FeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self) - typealias FeedMetadataDictionary = [String: FeedMetadata] - var feedMetadata = FeedMetadataDictionary() + private lazy var webFeedMetadataFile = WebFeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self) + typealias WebFeedMetadataDictionary = [String: WebFeedMetadata] + var webFeedMetadata = WebFeedMetadataDictionary() var startingUp = true @@ -259,11 +259,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil) metadataFile.load() - feedMetadataFile.load() + webFeedMetadataFile.load() opmlFile.load() DispatchQueue.main.async { - self.database.cleanupDatabaseAtStartup(subscribedToFeedIDs: self.flattenedFeeds().feedIDs()) + self.database.cleanupDatabaseAtStartup(subscribedToWebFeedIDs: self.flattenedWebFeeds().webFeedIDs()) self.fetchAllUnreadCounts() } @@ -395,17 +395,17 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, public func save() { metadataFile.save() - feedMetadataFile.save() + webFeedMetadataFile.save() opmlFile.save() } func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) { - var feedsToAdd = Set() + var feedsToAdd = Set() items.forEach { (item) in if let feedSpecifier = item.feedSpecifier { - let feed = newFeed(with: feedSpecifier) + let feed = newWebFeed(with: feedSpecifier) feedsToAdd.insert(feed) return } @@ -428,22 +428,22 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, if let parentFolder = parentFolder { for feed in feedsToAdd { - parentFolder.addFeed(feed) + parentFolder.addWebFeed(feed) } } else { for feed in feedsToAdd { - addFeed(feed) + addWebFeed(feed) } } } - public func resetFeedMetadataAndUnreadCounts() { - for feed in flattenedFeeds() { - feed.metadata = feedMetadata(feedURL: feed.url, feedID: feed.feedID) + public func resetWebFeedMetadataAndUnreadCounts() { + for feed in flattenedWebFeeds() { + feed.metadata = webFeedMetadata(feedURL: feed.url, webFeedID: feed.webFeedID) } fetchAllUnreadCounts() - NotificationCenter.default.post(name: .FeedMetadataDidChange, object: self, userInfo: nil) + NotificationCenter.default.post(name: .WebFeedMetadataDidChange, object: self, userInfo: nil) } public func markArticles(_ articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) -> Set
? { @@ -484,10 +484,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return folders?.first(where: { $0.nameForDisplay == displayName }) } - func newFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed { + func newWebFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> WebFeed { let feedURL = opmlFeedSpecifier.feedURL - let metadata = feedMetadata(feedURL: feedURL, feedID: feedURL) - let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata) + let metadata = webFeedMetadata(feedURL: feedURL, webFeedID: feedURL) + let feed = WebFeed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata) if let feedTitle = opmlFeedSpecifier.title { if feed.name == nil { feed.name = feedTitle @@ -496,37 +496,37 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return feed } - public func addFeed(_ feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { - delegate.addFeed(for: self, with: feed, to: container, completion: completion) + public func addWebFeed(_ feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { + delegate.addWebFeed(for: self, with: feed, to: container, completion: completion) } - public func createFeed(url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { - delegate.createFeed(for: self, url: url, name: name, container: container, completion: completion) + public func createWebFeed(url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { + delegate.createWebFeed(for: self, url: url, name: name, container: container, completion: completion) } - func createFeed(with name: String?, url: String, feedID: String, homePageURL: String?) -> Feed { - let metadata = feedMetadata(feedURL: url, feedID: feedID) - let feed = Feed(account: self, url: url, metadata: metadata) + func createWebFeed(with name: String?, url: String, webFeedID: String, homePageURL: String?) -> WebFeed { + let metadata = webFeedMetadata(feedURL: url, webFeedID: webFeedID) + let feed = WebFeed(account: self, url: url, metadata: metadata) feed.name = name feed.homePageURL = homePageURL return feed } - public func removeFeed(_ feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { - delegate.removeFeed(for: self, with: feed, from: container, completion: completion) + public func removeWebFeed(_ feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + delegate.removeWebFeed(for: self, with: feed, from: container, completion: completion) } - public func moveFeed(_ feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { - delegate.moveFeed(for: self, with: feed, from: from, to: to, completion: completion) + public func moveWebFeed(_ feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + delegate.moveWebFeed(for: self, with: feed, from: from, to: to, completion: completion) } - public func renameFeed(_ feed: Feed, to name: String, completion: @escaping (Result) -> Void) { - delegate.renameFeed(for: self, with: feed, to: name, completion: completion) + public func renameWebFeed(_ feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { + delegate.renameWebFeed(for: self, with: feed, to: name, completion: completion) } - public func restoreFeed(_ feed: Feed, container: Container, completion: @escaping (Result) -> Void) { - delegate.restoreFeed(for: self, feed: feed, container: container, completion: completion) + public func restoreWebFeed(_ feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + delegate.restoreWebFeed(for: self, feed: feed, container: container, completion: completion) } public func addFolder(_ name: String, completion: @escaping (Result) -> Void) { @@ -545,8 +545,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, delegate.restoreFolder(for: self, folder: folder, completion: completion) } - func clearFeedMetadata(_ feed: Feed) { - feedMetadata[feed.url] = nil + func clearWebFeedMetadata(_ feed: WebFeed) { + webFeedMetadata[feed.url] = nil } func addFolder(_ folder: Folder) { @@ -555,15 +555,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, structureDidChange() } - public func updateUnreadCounts(for feeds: Set) { - if feeds.isEmpty { + public func updateUnreadCounts(for webFeeds: Set) { + if webFeeds.isEmpty { return } - database.fetchUnreadCounts(for: feeds.feedIDs()) { (unreadCountDictionary) in - for feed in feeds { - if let unreadCount = unreadCountDictionary[feed.feedID] { - feed.unreadCount = unreadCount + database.fetchUnreadCounts(for: webFeeds.webFeedIDs()) { (unreadCountDictionary) in + for webFeed in webFeeds { + if let unreadCount = unreadCountDictionary[webFeed.webFeedID] { + webFeed.unreadCount = unreadCount } } } @@ -579,8 +579,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return fetchTodayArticles() case .unreadForFolder(let folder): return fetchArticles(folder: folder) - case .feed(let feed): - return fetchArticles(feed: feed) + case .webFeed(let webFeed): + return fetchArticles(webFeed: webFeed) case .articleIDs(let articleIDs): return fetchArticles(articleIDs: articleIDs) case .search(let searchString): @@ -600,8 +600,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, fetchTodayArticlesAsync(callback) case .unreadForFolder(let folder): fetchArticlesAsync(folder: folder, callback) - case .feed(let feed): - fetchArticlesAsync(feed: feed, callback) + case .webFeed(let webFeed): + fetchArticlesAsync(webFeed: webFeed, callback) case .articleIDs(let articleIDs): fetchArticlesAsync(articleIDs: articleIDs, callback) case .search(let searchString): @@ -612,11 +612,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } public func fetchUnreadCountForToday(_ callback: @escaping (Int) -> Void) { - database.fetchUnreadCountForToday(for: flattenedFeeds().feedIDs(), callback: callback) + database.fetchUnreadCountForToday(for: flattenedWebFeeds().webFeedIDs(), callback: callback) } public func fetchUnreadCountForStarredArticles(_ callback: @escaping (Int) -> Void) { - database.fetchStarredAndUnreadCount(for: flattenedFeeds().feedIDs(), callback: callback) + database.fetchStarredAndUnreadCount(for: flattenedWebFeeds().webFeedIDs(), callback: callback) } public func fetchUnreadArticleIDs() -> Set { @@ -631,12 +631,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return database.fetchArticleIDsForStatusesWithoutArticles() } - public func unreadCount(for feed: Feed) -> Int { - return unreadCounts[feed.feedID] ?? 0 + public func unreadCount(for webFeed: WebFeed) -> Int { + return unreadCounts[webFeed.webFeedID] ?? 0 } - public func setUnreadCount(_ unreadCount: Int, for feed: Feed) { - unreadCounts[feed.feedID] = unreadCount + public func setUnreadCount(_ unreadCount: Int, for webFeed: WebFeed) { + unreadCounts[webFeed.webFeedID] = unreadCount } public func structureDidChange() { @@ -645,36 +645,36 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, if !startingUp { opmlFile.markAsDirty() } - flattenedFeedsNeedUpdate = true - feedDictionaryNeedsUpdate = true + flattenedWebFeedsNeedUpdate = true + webFeedDictionaryNeedsUpdate = true } - func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) { + func update(_ webFeed: WebFeed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) { // Used only by an On My Mac account. - feed.takeSettings(from: parsedFeed) - let feedIDsAndItems = [feed.feedID: parsedFeed.items] - update(feedIDsAndItems: feedIDsAndItems, defaultRead: false, completion: completion) + webFeed.takeSettings(from: parsedFeed) + let webFeedIDsAndItems = [webFeed.webFeedID: parsedFeed.items] + update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: false, completion: completion) } - func update(feedIDsAndItems: [String: Set], defaultRead: Bool, completion: @escaping (() -> Void)) { + func update(webFeedIDsAndItems: [String: Set], defaultRead: Bool, completion: @escaping (() -> Void)) { assert(Thread.isMainThread) - guard !feedIDsAndItems.isEmpty else { + guard !webFeedIDsAndItems.isEmpty else { completion() return } - database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in + database.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in var userInfo = [String: Any]() - let feeds = Set(feedIDsAndItems.compactMap { (key, _) -> Feed? in - self.existingFeed(withFeedID: key) + let webFeeds = Set(webFeedIDsAndItems.compactMap { (key, _) -> WebFeed? in + self.existingWebFeed(withWebFeedID: key) }) if let newArticles = newArticles, !newArticles.isEmpty { - self.updateUnreadCounts(for: feeds) + self.updateUnreadCounts(for: webFeeds) userInfo[UserInfoKey.newArticles] = newArticles } if let updatedArticles = updatedArticles, !updatedArticles.isEmpty { userInfo[UserInfoKey.updatedArticles] = updatedArticles } - userInfo[UserInfoKey.feeds] = feeds + userInfo[UserInfoKey.webFeeds] = webFeeds completion() @@ -711,38 +711,38 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, // MARK: - Container - public func flattenedFeeds() -> Set { + public func flattenedWebFeeds() -> Set { assert(Thread.isMainThread) - if flattenedFeedsNeedUpdate { - updateFlattenedFeeds() + if flattenedWebFeedsNeedUpdate { + updateFlattenedWebFeeds() } - return _flattenedFeeds + return _flattenedWebFeeds } - public func removeFeed(_ feed: Feed) { - topLevelFeeds.remove(feed) + public func removeWebFeed(_ webFeed: WebFeed) { + topLevelWebFeeds.remove(webFeed) structureDidChange() postChildrenDidChangeNotification() } - public func removeFeeds(_ feeds: Set) { - guard !feeds.isEmpty else { + public func removeFeeds(_ webFeeds: Set) { + guard !webFeeds.isEmpty else { return } - topLevelFeeds.subtract(feeds) + topLevelWebFeeds.subtract(webFeeds) structureDidChange() postChildrenDidChangeNotification() } - public func addFeed(_ feed: Feed) { - topLevelFeeds.insert(feed) + public func addWebFeed(_ webFeed: WebFeed) { + topLevelWebFeeds.insert(webFeed) structureDidChange() postChildrenDidChangeNotification() } - func addFeedIfNotInAnyFolder(_ feed: Feed) { - if !flattenedFeeds().contains(feed) { - addFeed(feed) + func addFeedIfNotInAnyFolder(_ webFeed: WebFeed) { + if !flattenedWebFeeds().contains(webFeed) { + addWebFeed(webFeed) } } @@ -756,7 +756,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, public func debugDropConditionalGetInfo() { #if DEBUG - flattenedFeeds().forEach{ $0.debugDropConditionalGetInfo() } + flattenedWebFeeds().forEach{ $0.debugDropConditionalGetInfo() } #endif } @@ -782,14 +782,14 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } @objc func unreadCountDidChange(_ note: Notification) { - if let feed = note.object as? Feed, feed.account === self { + if let feed = note.object as? WebFeed, feed.account === self { updateUnreadCount() } } @objc func batchUpdateDidPerform(_ note: Notification) { - flattenedFeedsNeedUpdate = true - rebuildFeedDictionaries() + flattenedWebFeedsNeedUpdate = true + rebuildWebFeedDictionaries() updateUnreadCount() } @@ -835,11 +835,11 @@ extension Account: AccountMetadataDelegate { // MARK: - FeedMetadataDelegate -extension Account: FeedMetadataDelegate { +extension Account: WebFeedMetadataDelegate { - func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) { - feedMetadataFile.markAsDirty() - guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else { + func valueDidChange(_ feedMetadata: WebFeedMetadata, key: WebFeedMetadata.CodingKeys) { + webFeedMetadataFile.markAsDirty() + guard let feed = existingWebFeed(withWebFeedID: feedMetadata.webFeedID) else { return } feed.postFeedSettingDidChangeNotification(key) @@ -851,11 +851,11 @@ extension Account: FeedMetadataDelegate { private extension Account { func fetchStarredArticles() -> Set
{ - return database.fetchStarredArticles(flattenedFeeds().feedIDs()) + return database.fetchStarredArticles(flattenedWebFeeds().webFeedIDs()) } func fetchStarredArticlesAsync(_ callback: @escaping ArticleSetBlock) { - database.fetchedStarredArticlesAsync(flattenedFeeds().feedIDs(), callback) + database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs(), callback) } func fetchUnreadArticles() -> Set
{ @@ -867,11 +867,11 @@ private extension Account { } func fetchTodayArticles() -> Set
{ - return database.fetchTodayArticles(flattenedFeeds().feedIDs()) + return database.fetchTodayArticles(flattenedWebFeeds().webFeedIDs()) } func fetchTodayArticlesAsync(_ callback: @escaping ArticleSetBlock) { - database.fetchTodayArticlesAsync(flattenedFeeds().feedIDs(), callback) + database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs(), callback) } func fetchArticles(folder: Folder) -> Set
{ @@ -882,21 +882,21 @@ private extension Account { fetchUnreadArticlesAsync(forContainer: folder, callback) } - func fetchArticles(feed: Feed) -> Set
{ - let articles = database.fetchArticles(feed.feedID) - validateUnreadCount(feed, articles) + func fetchArticles(webFeed: WebFeed) -> Set
{ + let articles = database.fetchArticles(webFeed.webFeedID) + validateUnreadCount(webFeed, articles) return articles } - func fetchArticlesAsync(feed: Feed, _ callback: @escaping ArticleSetBlock) { - database.fetchArticlesAsync(feed.feedID) { [weak self] (articles) in - self?.validateUnreadCount(feed, articles) + func fetchArticlesAsync(webFeed: WebFeed, _ callback: @escaping ArticleSetBlock) { + database.fetchArticlesAsync(webFeed.webFeedID) { [weak self] (articles) in + self?.validateUnreadCount(webFeed, articles) callback(articles) } } func fetchArticlesMatching(_ searchString: String) -> Set
{ - return database.fetchArticlesMatching(searchString, flattenedFeeds().feedIDs()) + return database.fetchArticlesMatching(searchString, flattenedWebFeeds().webFeedIDs()) } func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set) -> Set
{ @@ -904,7 +904,7 @@ private extension Account { } func fetchArticlesMatchingAsync(_ searchString: String, _ callback: @escaping ArticleSetBlock) { - database.fetchArticlesMatchingAsync(searchString, flattenedFeeds().feedIDs(), callback) + database.fetchArticlesMatchingAsync(searchString, flattenedWebFeeds().webFeedIDs(), callback) } func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set, _ callback: @escaping ArticleSetBlock) { @@ -919,13 +919,13 @@ private extension Account { return database.fetchArticlesAsync(articleIDs: articleIDs, callback) } - func fetchUnreadArticles(feed: Feed) -> Set
{ - let articles = database.fetchUnreadArticles(Set([feed.feedID])) - validateUnreadCount(feed, articles) + func fetchUnreadArticles(webFeed: WebFeed) -> Set
{ + let articles = database.fetchUnreadArticles(Set([webFeed.webFeedID])) + validateUnreadCount(webFeed, articles) return articles } - func fetchUnreadArticlesAsync(for feed: Feed, callback: @escaping (Set
) -> Void) { + func fetchUnreadArticlesAsync(for webFeed: WebFeed, callback: @escaping (Set
) -> Void) { // database.fetchUnreadArticlesAsync(for: Set([feed.feedID])) { [weak self] (articles) in // self?.validateUnreadCount(feed, articles) // callback(articles) @@ -934,48 +934,48 @@ private extension Account { func fetchUnreadArticles(forContainer container: Container) -> Set
{ - let feeds = container.flattenedFeeds() - let articles = database.fetchUnreadArticles(feeds.feedIDs()) + let feeds = container.flattenedWebFeeds() + let articles = database.fetchUnreadArticles(feeds.webFeedIDs()) validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles) return articles } func fetchUnreadArticlesAsync(forContainer container: Container, _ callback: @escaping ArticleSetBlock) { - let feeds = container.flattenedFeeds() - database.fetchUnreadArticlesAsync(feeds.feedIDs()) { [weak self] (articles) in - self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles) + let webFeeds = container.flattenedWebFeeds() + database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articles) in + self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles) callback(articles) } } - func validateUnreadCountsAfterFetchingUnreadArticles(_ feeds: Set, _ articles: Set
) { + func validateUnreadCountsAfterFetchingUnreadArticles(_ webFeeds: Set, _ articles: Set
) { // Validate unread counts. This was the site of a performance slowdown: // it was calling going through the entire list of articles once per feed: // feeds.forEach { validateUnreadCount($0, articles) } // Now we loop through articles exactly once. This makes a huge difference. - var unreadCountStorage = [String: Int]() // [FeedID: Int] + var unreadCountStorage = [String: Int]() // [WebFeedID: Int] for article in articles where !article.status.read { - unreadCountStorage[article.feedID, default: 0] += 1 + unreadCountStorage[article.webFeedID, default: 0] += 1 } - feeds.forEach { (feed) in - let unreadCount = unreadCountStorage[feed.feedID, default: 0] - feed.unreadCount = unreadCount + webFeeds.forEach { (webFeed) in + let unreadCount = unreadCountStorage[webFeed.webFeedID, default: 0] + webFeed.unreadCount = unreadCount } } - func validateUnreadCount(_ feed: Feed, _ articles: Set
) { + func validateUnreadCount(_ webFeed: WebFeed, _ articles: Set
) { // articles must contain all the unread articles for the feed. // The unread number should match the feed’s unread count. let feedUnreadCount = articles.reduce(0) { (result, article) -> Int in - if article.feed == feed && !article.status.read { + if article.webFeed == webFeed && !article.status.read { return result + 1 } return result } - feed.unreadCount = feedUnreadCount + webFeed.unreadCount = feedUnreadCount } } @@ -983,37 +983,37 @@ private extension Account { private extension Account { - func feedMetadata(feedURL: String, feedID: String) -> FeedMetadata { - if let d = feedMetadata[feedURL] { + func webFeedMetadata(feedURL: String, webFeedID: String) -> WebFeedMetadata { + if let d = webFeedMetadata[feedURL] { assert(d.delegate === self) return d } - let d = FeedMetadata(feedID: feedID) + let d = WebFeedMetadata(webFeedID: webFeedID) d.delegate = self - feedMetadata[feedURL] = d + webFeedMetadata[feedURL] = d return d } - func updateFlattenedFeeds() { - var feeds = Set() - feeds.formUnion(topLevelFeeds) + func updateFlattenedWebFeeds() { + var feeds = Set() + feeds.formUnion(topLevelWebFeeds) for folder in folders! { - feeds.formUnion(folder.flattenedFeeds()) + feeds.formUnion(folder.flattenedWebFeeds()) } - _flattenedFeeds = feeds - flattenedFeedsNeedUpdate = false + _flattenedWebFeeds = feeds + flattenedWebFeedsNeedUpdate = false } - func rebuildFeedDictionaries() { - var idDictionary = [String: Feed]() + func rebuildWebFeedDictionaries() { + var idDictionary = [String: WebFeed]() - flattenedFeeds().forEach { (feed) in - idDictionary[feed.feedID] = feed + flattenedWebFeeds().forEach { (feed) in + idDictionary[feed.webFeedID] = feed } - _idToFeedDictionary = idDictionary - feedDictionaryNeedsUpdate = false + _idToWebFeedDictionary = idDictionary + webFeedDictionaryNeedsUpdate = false } func updateUnreadCount() { @@ -1021,21 +1021,21 @@ private extension Account { return } var updatedUnreadCount = 0 - for feed in flattenedFeeds() { + for feed in flattenedWebFeeds() { updatedUnreadCount += feed.unreadCount } unreadCount = updatedUnreadCount } func noteStatusesForArticlesDidChange(_ articles: Set
) { - let feeds = Set(articles.compactMap { $0.feed }) + let feeds = Set(articles.compactMap { $0.webFeed }) let statuses = Set(articles.map { $0.status }) // .UnreadCountDidChange notification will get sent to Folder and Account objects, // which will update their own unread counts. updateUnreadCounts(for: feeds) - NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: statuses, UserInfoKey.articles: articles, UserInfoKey.feeds: feeds]) + NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: statuses, UserInfoKey.articles: articles, UserInfoKey.webFeeds: feeds]) } func fetchAllUnreadCounts() { @@ -1049,10 +1049,10 @@ private extension Account { return } - self.flattenedFeeds().forEach{ (feed) in + self.flattenedWebFeeds().forEach{ (feed) in // When the unread count is zero, it won’t appear in unreadCountDictionary. - if let unreadCount = unreadCountDictionary[feed.feedID] { + if let unreadCount = unreadCountDictionary[feed.webFeedID] { feed.unreadCount = unreadCount } else { @@ -1070,8 +1070,8 @@ private extension Account { extension Account { - public func existingFeed(withFeedID feedID: String) -> Feed? { - return idToFeedDictionary[feedID] + public func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? { + return idToWebFeedDictionary[webFeedID] } } @@ -1081,7 +1081,7 @@ extension Account: OPMLRepresentable { public func OPMLString(indentLevel: Int, strictConformance: Bool) -> String { var s = "" - for feed in topLevelFeeds { + for feed in topLevelWebFeeds { s += feed.OPMLString(indentLevel: indentLevel + 1, strictConformance: strictConformance) } for folder in folders! { diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 5f793a3c2..090d9d563 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -11,7 +11,7 @@ 5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */; }; 5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; }; 510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; }; - 510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */; }; + 510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */; }; 511B9804237CD4270028BCAA /* ArticleFetcherType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */; }; 513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; }; 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; }; @@ -56,7 +56,7 @@ 841D4D702106B40400DD04E6 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841D4D6F2106B40400DD04E6 /* ArticlesDatabase.framework */; }; 841D4D722106B40A00DD04E6 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841D4D712106B40A00DD04E6 /* Articles.framework */; }; 84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */; }; - 844B297D2106C7EC004020B3 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297C2106C7EC004020B3 /* Feed.swift */; }; + 844B297D2106C7EC004020B3 /* WebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297C2106C7EC004020B3 /* WebFeed.swift */; }; 844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */; }; 844B2981210CE3BF004020B3 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844B2980210CE3BF004020B3 /* RSWeb.framework */; }; 8469F81C1F6DD15E0084783E /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935101F62486800CEBD24 /* Account.swift */; }; @@ -65,7 +65,7 @@ 846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419742D1F6DDE96006346C4 /* LocalAccountRefresher.swift */; }; 846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E77531F6F00E300A165E2 /* AccountManager.swift */; }; 848935001F62484F00CEBD24 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 848934F61F62484F00CEBD24 /* Account.framework */; }; - 84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */; }; + 84B2D4D02238CD8A00498ADA /* WebFeedMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */; }; 84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */; }; 84C3654A1F899F3B001EC85C /* CombinedRefreshProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */; }; 84C8B3F41F89DE430053CCA6 /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */; }; @@ -209,7 +209,7 @@ 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAccountManager.swift; sourceTree = ""; }; 5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = ""; }; 510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = ""; }; - 510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedMetadataFile.swift; sourceTree = ""; }; + 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadataFile.swift; sourceTree = ""; }; 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleFetcherType.swift; sourceTree = ""; }; 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = ""; }; 513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = ""; }; @@ -258,7 +258,7 @@ 841D4D6F2106B40400DD04E6 /* ArticlesDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ArticlesDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 841D4D712106B40A00DD04E6 /* Articles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Articles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinSubscription.swift; sourceTree = ""; }; - 844B297C2106C7EC004020B3 /* Feed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = ""; }; + 844B297C2106C7EC004020B3 /* WebFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebFeed.swift; sourceTree = ""; }; 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountProvider.swift; sourceTree = ""; }; 844B2980210CE3BF004020B3 /* RSWeb.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSWeb.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 846E77531F6F00E300A165E2 /* AccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; @@ -268,7 +268,7 @@ 848935061F62485000CEBD24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 848935101F62486800CEBD24 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; 84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadata.swift; sourceTree = ""; }; - 84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedMetadata.swift; sourceTree = ""; }; + 84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadata.swift; sourceTree = ""; }; 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerPath.swift; sourceTree = ""; }; 84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedRefreshProgress.swift; sourceTree = ""; }; 84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtensions.swift; sourceTree = ""; }; @@ -532,9 +532,9 @@ 8419740D1F6DD25F006346C4 /* Container.swift */, 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */, 84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */, - 844B297C2106C7EC004020B3 /* Feed.swift */, - 84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */, - 510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */, + 844B297C2106C7EC004020B3 /* WebFeed.swift */, + 84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */, + 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */, 841974001F6DD1EC006346C4 /* Folder.swift */, 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */, 5165D71F22835E9800D9D53D /* FeedFinder */, @@ -964,7 +964,7 @@ 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */, 9E713653233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift in Sources */, 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, - 510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */, + 510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */, 5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */, 9EEEF71F23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift in Sources */, 846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */, @@ -976,7 +976,7 @@ 9E84DC492359A73600D6E809 /* FeedlyCheckpointOperation.swift in Sources */, 9E85C8EB236700E600D0F1F7 /* FeedlyGetEntriesOperation.swift in Sources */, 9E1D154D233370D800F4944C /* FeedlySyncAllOperation.swift in Sources */, - 844B297D2106C7EC004020B3 /* Feed.swift in Sources */, + 844B297D2106C7EC004020B3 /* WebFeed.swift in Sources */, 9E964EBA23754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift in Sources */, 9E1D15572334355900F4944C /* FeedlyRequestStreamsOperation.swift in Sources */, 9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */, @@ -987,7 +987,7 @@ 9EEAE075235D01C400E3FEE4 /* FeedlyMarkArticlesService.swift in Sources */, 9EF1B10323584B4C000A486A /* FeedlySyncStreamContentsOperation.swift in Sources */, 5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */, - 84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */, + 84B2D4D02238CD8A00498ADA /* WebFeedMetadata.swift in Sources */, 9E84DC472359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift in Sources */, 9EAEC624233315F60085D7C9 /* FeedlyEntry.swift in Sources */, 9EEAE073235D01AE00E3FEE4 /* FeedlyGetStreamIdsService.swift in Sources */, diff --git a/Frameworks/Account/AccountDelegate.swift b/Frameworks/Account/AccountDelegate.swift index de13bf9cb..4241a75f6 100644 --- a/Frameworks/Account/AccountDelegate.swift +++ b/Frameworks/Account/AccountDelegate.swift @@ -33,13 +33,13 @@ protocol AccountDelegate { func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) - func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) - func addFeed(for account: Account, with: Feed, to container: Container, completion: @escaping (Result) -> Void) - func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) - func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) + func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) + func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) + func addWebFeed(for account: Account, with: WebFeed, to container: Container, completion: @escaping (Result) -> Void) + func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) + func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) - func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) + func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) func markArticles(for account: Account, articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) -> Set
? diff --git a/Frameworks/Account/AccountManager.swift b/Frameworks/Account/AccountManager.swift index 385b5e389..0432e9aa1 100644 --- a/Frameworks/Account/AccountManager.swift +++ b/Frameworks/Account/AccountManager.swift @@ -207,7 +207,7 @@ public final class AccountManager: UnreadCountProvider { public func anyAccountHasAtLeastOneFeed() -> Bool { for account in activeAccounts { - if account.hasAtLeastOneFeed() { + if account.hasAtLeastOneWebFeed() { return true } } @@ -217,7 +217,7 @@ public final class AccountManager: UnreadCountProvider { public func anyAccountHasFeedWithURL(_ urlString: String) -> Bool { for account in activeAccounts { - if let _ = account.existingFeed(withURL: urlString) { + if let _ = account.existingWebFeed(withURL: urlString) { return true } } diff --git a/Frameworks/Account/AccountTests/Feedbin/AccountFeedbinFolderContentsSyncTest.swift b/Frameworks/Account/AccountTests/Feedbin/AccountFeedbinFolderContentsSyncTest.swift index f5cb6882d..1f73639d0 100644 --- a/Frameworks/Account/AccountTests/Feedbin/AccountFeedbinFolderContentsSyncTest.swift +++ b/Frameworks/Account/AccountTests/Feedbin/AccountFeedbinFolderContentsSyncTest.swift @@ -33,8 +33,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase { waitForExpectations(timeout: 5, handler: nil) let folder = account.folders?.filter { $0.name == "Developers" } .first! - XCTAssertEqual(156, folder?.topLevelFeeds.count ?? 0) - XCTAssertEqual(2, account.topLevelFeeds.count) + XCTAssertEqual(156, folder?.topLevelWebFeeds.count ?? 0) + XCTAssertEqual(2, account.topLevelWebFeeds.count) // Test Adding a Feed to the folder testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "taggings_add.json" @@ -45,8 +45,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase { } waitForExpectations(timeout: 5, handler: nil) - XCTAssertEqual(157, folder?.topLevelFeeds.count ?? 0) - XCTAssertEqual(1, account.topLevelFeeds.count) + XCTAssertEqual(157, folder?.topLevelWebFeeds.count ?? 0) + XCTAssertEqual(1, account.topLevelWebFeeds.count) // Test Deleting some Feeds from the folder testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "taggings_delete.json" @@ -57,8 +57,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase { } waitForExpectations(timeout: 5, handler: nil) - XCTAssertEqual(153, folder?.topLevelFeeds.count ?? 0) - XCTAssertEqual(5, account.topLevelFeeds.count) + XCTAssertEqual(153, folder?.topLevelWebFeeds.count ?? 0) + XCTAssertEqual(5, account.topLevelWebFeeds.count) TestAccountManager.shared.deleteAccount(account) diff --git a/Frameworks/Account/AccountTests/Feedbin/AccountFeedbinSyncTest.swift b/Frameworks/Account/AccountTests/Feedbin/AccountFeedbinSyncTest.swift index ea3e5339d..d745aa1e1 100644 --- a/Frameworks/Account/AccountTests/Feedbin/AccountFeedbinSyncTest.swift +++ b/Frameworks/Account/AccountTests/Feedbin/AccountFeedbinSyncTest.swift @@ -36,9 +36,9 @@ class AccountFeedbinSyncTest: XCTestCase { } waitForExpectations(timeout: 5, handler: nil) - XCTAssertEqual(224, account.flattenedFeeds().count) + XCTAssertEqual(224, account.flattenedWebFeeds().count) - let daringFireball = account.idToFeedDictionary["1296379"] + let daringFireball = account.idToWebFeedDictionary["1296379"] XCTAssertEqual("Daring Fireball", daringFireball!.name) XCTAssertEqual("https://daringfireball.net/feeds/json", daringFireball!.url) XCTAssertEqual("https://daringfireball.net/", daringFireball!.homePageURL) @@ -57,9 +57,9 @@ class AccountFeedbinSyncTest: XCTestCase { } waitForExpectations(timeout: 5, handler: nil) - XCTAssertEqual(225, account.flattenedFeeds().count) + XCTAssertEqual(225, account.flattenedWebFeeds().count) - let bPixels = account.idToFeedDictionary["1096623"] + let bPixels = account.idToWebFeedDictionary["1096623"] XCTAssertEqual("Beautiful Pixels", bPixels?.name) XCTAssertEqual("https://feedpress.me/beautifulpixels", bPixels?.url) XCTAssertEqual("https://beautifulpixels.com/", bPixels?.homePageURL) diff --git a/Frameworks/Account/AccountTests/Feedly/FeedlyCreateFeedsForCollectionFoldersOperationTests.swift b/Frameworks/Account/AccountTests/Feedly/FeedlyCreateFeedsForCollectionFoldersOperationTests.swift index d496c9d6c..408e093db 100644 --- a/Frameworks/Account/AccountTests/Feedly/FeedlyCreateFeedsForCollectionFoldersOperationTests.swift +++ b/Frameworks/Account/AccountTests/Feedly/FeedlyCreateFeedsForCollectionFoldersOperationTests.swift @@ -58,7 +58,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase { completionExpectation.fulfill() } - XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.") + XCTAssertTrue(account.flattenedWebFeeds().isEmpty, "Expected empty account.") OperationQueue.main.addOperation(createFeeds) @@ -72,8 +72,8 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase { .flatMap { $0 } .map { $0.title }) - let accountFeeds = account.flattenedFeeds() - let ingestedIds = Set(accountFeeds.map { $0.feedID }) + let accountFeeds = account.flattenedWebFeeds() + let ingestedIds = Set(accountFeeds.map { $0.webFeedID }) let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay }) let missingIds = feedIds.subtracting(ingestedIds) @@ -91,7 +91,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase { let ingestedFolderAndFeedIds = (account.folders ?? Set()) .sorted { $0.externalID! < $1.externalID! } .compactMap { folder -> [String: [String]]? in - return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)] + return [folder.externalID!: folder.topLevelWebFeeds.map { $0.webFeedID }.sorted(by: <)] } XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds in their corresponding folders.") @@ -129,7 +129,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase { completionExpectation.fulfill() } - XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.") + XCTAssertTrue(account.flattenedWebFeeds().isEmpty, "Expected empty account.") OperationQueue.main.addOperation(createFeeds) @@ -165,8 +165,8 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase { .flatMap { $0 } .map { $0.title }) - let accountFeeds = account.flattenedFeeds() - let ingestedIds = Set(accountFeeds.map { $0.feedID }) + let accountFeeds = account.flattenedWebFeeds() + let ingestedIds = Set(accountFeeds.map { $0.webFeedID }) let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay }) XCTAssertEqual(ingestedIds.count, feedIds.count) @@ -187,7 +187,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase { let ingestedFolderAndFeedIds = (account.folders ?? Set()) .sorted { $0.externalID! < $1.externalID! } .compactMap { folder -> [String: [String]]? in - return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)] + return [folder.externalID!: folder.topLevelWebFeeds.map { $0.webFeedID }.sorted(by: <)] } XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds to their corresponding folders.") diff --git a/Frameworks/Account/AccountTests/Feedly/FeedlyMirrorCollectionsAsFoldersOperationTests.swift b/Frameworks/Account/AccountTests/Feedly/FeedlyMirrorCollectionsAsFoldersOperationTests.swift index 6386738e9..ae6dd5dec 100644 --- a/Frameworks/Account/AccountTests/Feedly/FeedlyMirrorCollectionsAsFoldersOperationTests.swift +++ b/Frameworks/Account/AccountTests/Feedly/FeedlyMirrorCollectionsAsFoldersOperationTests.swift @@ -183,7 +183,7 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase { waitForExpectations(timeout: 2) - XCTAssertFalse(account.flattenedFeeds().isEmpty, "Expected account to have feeds.") + XCTAssertFalse(account.flattenedWebFeeds().isEmpty, "Expected account to have feeds.") } // Now that the folders are added, remove them all. @@ -200,7 +200,7 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase { waitForExpectations(timeout: 2) - let feeds = account.flattenedFeeds() + let feeds = account.flattenedWebFeeds() XCTAssertTrue(feeds.isEmpty) } diff --git a/Frameworks/Account/AccountTests/Feedly/FeedlyTestSupport.swift b/Frameworks/Account/AccountTests/Feedly/FeedlyTestSupport.swift index ffb0e8660..ea1e79f26 100644 --- a/Frameworks/Account/AccountTests/Feedly/FeedlyTestSupport.swift +++ b/Frameworks/Account/AccountTests/Feedly/FeedlyTestSupport.swift @@ -129,12 +129,12 @@ class FeedlyTestSupport { return } let collectionFeeds = collection["feeds"] as! [[String: Any]] - let folderFeeds = folder.topLevelFeeds + let folderFeeds = folder.topLevelWebFeeds XCTAssertEqual(collectionFeeds.count, folderFeeds.count) let collectionFeedIds = Set(collectionFeeds.map { $0["id"] as! String }) - let folderFeedIds = Set(folderFeeds.map { $0.feedID }) + let folderFeedIds = Set(folderFeeds.map { $0.webFeedID }) let missingFeedIds = collectionFeedIds.subtracting(folderFeedIds) XCTAssertTrue(missingFeedIds.isEmpty, "Feeds with these ids were not found in the \"\(label)\" folder.") @@ -205,7 +205,7 @@ class FeedlyTestSupport { for item in articleItems where item.id == article.articleID { XCTAssertEqual(article.uniqueID, item.id) XCTAssertEqual(article.contentHTML, item.content) - XCTAssertEqual(article.feedID, item.feedId) + XCTAssertEqual(article.webFeedID, item.feedId) XCTAssertEqual(article.externalURL, item.externalUrl) } } diff --git a/Frameworks/Account/ArticleFetcher.swift b/Frameworks/Account/ArticleFetcher.swift index a4bb9c015..43509b866 100644 --- a/Frameworks/Account/ArticleFetcher.swift +++ b/Frameworks/Account/ArticleFetcher.swift @@ -19,18 +19,18 @@ public protocol ArticleFetcher { func fetchUnreadArticlesAsync(_ callback: @escaping ArticleSetBlock) } -extension Feed: ArticleFetcher { +extension WebFeed: ArticleFetcher { public var articleFetcherType: ArticleFetcherType? { guard let accountID = account?.accountID else { assertionFailure("Expected feed.account, but got nil.") return nil } - return ArticleFetcherType.feed(accountID, feedID) + return ArticleFetcherType.webFeed(accountID, webFeedID) } public func fetchArticles() -> Set
{ - return account?.fetchArticles(.feed(self)) ?? Set
() + return account?.fetchArticles(.webFeed(self)) ?? Set
() } public func fetchArticlesAsync(_ callback: @escaping ArticleSetBlock) { @@ -39,7 +39,7 @@ extension Feed: ArticleFetcher { callback(Set
()) return } - account.fetchArticlesAsync(.feed(self), callback) + account.fetchArticlesAsync(.webFeed(self), callback) } public func fetchUnreadArticles() -> Set
{ @@ -52,7 +52,7 @@ extension Feed: ArticleFetcher { callback(Set
()) return } - account.fetchArticlesAsync(.feed(self)) { callback($0.unreadArticles()) } + account.fetchArticlesAsync(.webFeed(self)) { callback($0.unreadArticles()) } } } diff --git a/Frameworks/Account/ArticleFetcherType.swift b/Frameworks/Account/ArticleFetcherType.swift index b030c5da4..02912fb5f 100644 --- a/Frameworks/Account/ArticleFetcherType.swift +++ b/Frameworks/Account/ArticleFetcherType.swift @@ -12,7 +12,7 @@ public enum ArticleFetcherType: CustomStringConvertible { case smartFeed(String) // String is a unique identifier case script(String) // String is a unique identifier - case feed(String, String) // accountID, feedID + case webFeed(String, String) // accountID, webFeedID case folder(String, String) // accountID, folderName public var description: String { @@ -21,8 +21,8 @@ public enum ArticleFetcherType: CustomStringConvertible { return "smartFeed: \(id)" case .script(let id): return "script: \(id)" - case .feed(let accountID, let feedID): - return "feed: \(accountID)_\(feedID)" + case .webFeed(let accountID, let webFeedID): + return "feed: \(accountID)_\(webFeedID)" case .folder(let accountID, let folderName): return "folder: \(accountID)_\(folderName)" } @@ -40,11 +40,11 @@ public enum ArticleFetcherType: CustomStringConvertible { "type": "script", "id": id ] - case .feed(let accountID, let feedID): + case .webFeed(let accountID, let webFeedID): return [ "type": "feed", "accountID": accountID, - "feedID": feedID + "webFeedID": webFeedID ] case .folder(let accountID, let folderName): return [ @@ -66,8 +66,8 @@ public enum ArticleFetcherType: CustomStringConvertible { guard let id = userInfo["id"] as? String else { return nil } self = ArticleFetcherType.script(id) case "feed": - guard let accountID = userInfo["accountID"] as? String, let feedID = userInfo["feedID"] as? String else { return nil } - self = ArticleFetcherType.feed(accountID, feedID) + guard let accountID = userInfo["accountID"] as? String, let webFeedID = userInfo["webFeedID"] as? String else { return nil } + self = ArticleFetcherType.webFeed(accountID, webFeedID) case "folder": guard let accountID = userInfo["accountID"] as? String, let folderName = userInfo["folderName"] as? String else { return nil } self = ArticleFetcherType.folder(accountID, folderName) diff --git a/Frameworks/Account/Container.swift b/Frameworks/Account/Container.swift index 4124e8314..4c7d36e85 100644 --- a/Frameworks/Account/Container.swift +++ b/Frameworks/Account/Container.swift @@ -19,25 +19,25 @@ extension Notification.Name { public protocol Container: class { var account: Account? { get } - var topLevelFeeds: Set { get set } + var topLevelWebFeeds: Set { get set } var folders: Set? { get set } - func hasAtLeastOneFeed() -> Bool + func hasAtLeastOneWebFeed() -> Bool func objectIsChild(_ object: AnyObject) -> Bool func hasChildFolder(with: String) -> Bool func childFolder(with: String) -> Folder? - func removeFeed(_ feed: Feed) - func addFeed(_ feed: Feed) + func removeWebFeed(_ webFeed: WebFeed) + func addWebFeed(_ webFeed: WebFeed) //Recursive — checks subfolders - func flattenedFeeds() -> Set - func has(_ feed: Feed) -> Bool - func hasFeed(with feedID: String) -> Bool - func hasFeed(withURL url: String) -> Bool - func existingFeed(withFeedID: String) -> Feed? - func existingFeed(withURL url: String) -> Feed? + func flattenedWebFeeds() -> Set + func has(_ webFeed: WebFeed) -> Bool + func hasWebFeed(with webFeedID: String) -> Bool + func hasWebFeed(withURL url: String) -> Bool + func existingWebFeed(withWebFeedID: String) -> WebFeed? + func existingWebFeed(withURL url: String) -> WebFeed? func existingFolder(with name: String) -> Folder? func existingFolder(withID: Int) -> Folder? @@ -46,8 +46,8 @@ public protocol Container: class { public extension Container { - func hasAtLeastOneFeed() -> Bool { - return topLevelFeeds.count > 0 + func hasAtLeastOneWebFeed() -> Bool { + return topLevelWebFeeds.count > 0 } func hasChildFolder(with name: String) -> Bool { @@ -67,8 +67,8 @@ public extension Container { } func objectIsChild(_ object: AnyObject) -> Bool { - if let feed = object as? Feed { - return topLevelFeeds.contains(feed) + if let feed = object as? WebFeed { + return topLevelWebFeeds.contains(feed) } if let folder = object as? Folder { return folders?.contains(folder) ?? false @@ -76,40 +76,40 @@ public extension Container { return false } - func flattenedFeeds() -> Set { - var feeds = Set() - feeds.formUnion(topLevelFeeds) + func flattenedWebFeeds() -> Set { + var feeds = Set() + feeds.formUnion(topLevelWebFeeds) if let folders = folders { for folder in folders { - feeds.formUnion(folder.flattenedFeeds()) + feeds.formUnion(folder.flattenedWebFeeds()) } } return feeds } - func hasFeed(with feedID: String) -> Bool { - return existingFeed(withFeedID: feedID) != nil + func hasWebFeed(with webFeedID: String) -> Bool { + return existingWebFeed(withWebFeedID: webFeedID) != nil } - func hasFeed(withURL url: String) -> Bool { - return existingFeed(withURL: url) != nil + func hasWebFeed(withURL url: String) -> Bool { + return existingWebFeed(withURL: url) != nil } - func has(_ feed: Feed) -> Bool { - return flattenedFeeds().contains(feed) + func has(_ webFeed: WebFeed) -> Bool { + return flattenedWebFeeds().contains(webFeed) } - func existingFeed(withFeedID feedID: String) -> Feed? { - for feed in flattenedFeeds() { - if feed.feedID == feedID { + func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? { + for feed in flattenedWebFeeds() { + if feed.webFeedID == webFeedID { return feed } } return nil } - func existingFeed(withURL url: String) -> Feed? { - for feed in flattenedFeeds() { + func existingWebFeed(withURL url: String) -> WebFeed? { + for feed in flattenedWebFeeds() { if feed.url == url { return feed } diff --git a/Frameworks/Account/DataExtensions.swift b/Frameworks/Account/DataExtensions.swift index a503ea62d..61ddc93cd 100644 --- a/Frameworks/Account/DataExtensions.swift +++ b/Frameworks/Account/DataExtensions.swift @@ -11,14 +11,14 @@ import Articles import RSParser public extension Notification.Name { - static let FeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification") + static let WebFeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification") } -public extension Feed { +public extension WebFeed { - static let FeedSettingUserInfoKey = "feedSetting" + static let WebFeedSettingUserInfoKey = "feedSetting" - struct FeedSettingKey { + struct WebFeedSettingKey { public static let homePageURL = "homePageURL" public static let iconURL = "iconURL" public static let faviconURL = "faviconURL" @@ -30,7 +30,7 @@ public extension Feed { } } -extension Feed { +extension WebFeed { func takeSettings(from parsedFeed: ParsedFeed) { iconURL = parsedFeed.iconURL @@ -40,9 +40,9 @@ extension Feed { authors = Author.authorsWithParsedAuthors(parsedFeed.authors) } - func postFeedSettingDidChangeNotification(_ codingKey: FeedMetadata.CodingKeys) { - let userInfo = [Feed.FeedSettingUserInfoKey: codingKey.stringValue] - NotificationCenter.default.post(name: .FeedSettingDidChange, object: self, userInfo: userInfo) + func postFeedSettingDidChangeNotification(_ codingKey: WebFeedMetadata.CodingKeys) { + let userInfo = [WebFeed.WebFeedSettingUserInfoKey: codingKey.stringValue] + NotificationCenter.default.post(name: .WebFeedSettingDidChange, object: self, userInfo: userInfo) } } @@ -56,8 +56,8 @@ public extension Article { return manager.existingAccount(with: accountID) } - var feed: Feed? { - return account?.existingFeed(withFeedID: feedID) + var webFeed: WebFeed? { + return account?.existingWebFeed(withWebFeedID: webFeedID) } } diff --git a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift index 6711a0d89..912187cde 100644 --- a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift +++ b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift @@ -275,7 +275,7 @@ final class FeedbinAPICaller: NSObject { } - func createTagging(feedID: Int, name: String, completion: @escaping (Result) -> Void) { + func createTagging(webFeedID: Int, name: String, completion: @escaping (Result) -> Void) { let callURL = feedbinBaseURL.appendingPathComponent("taggings.json") var request = URLRequest(url: callURL, credentials: credentials) @@ -283,7 +283,7 @@ final class FeedbinAPICaller: NSObject { let payload: Data do { - payload = try JSONEncoder().encode(FeedbinCreateTagging(feedID: feedID, name: name)) + payload = try JSONEncoder().encode(FeedbinCreateTagging(feedID: webFeedID, name: name)) } catch { completion(.failure(error)) return diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index ea3027a4f..79b344e49 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -268,7 +268,7 @@ final class FeedbinAccountDelegate: AccountDelegate { func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { - guard folder.hasAtLeastOneFeed() else { + guard folder.hasAtLeastOneWebFeed() else { folder.name = name return } @@ -296,7 +296,7 @@ final class FeedbinAccountDelegate: AccountDelegate { func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { // Feedbin uses tags and if at least one feed isn't tagged, then the folder doesn't exist on their system - guard folder.hasAtLeastOneFeed() else { + guard folder.hasAtLeastOneWebFeed() else { account.removeFolder(folder) completion(.success(())) return @@ -304,7 +304,7 @@ final class FeedbinAccountDelegate: AccountDelegate { let group = DispatchGroup() - for feed in folder.topLevelFeeds { + for feed in folder.topLevelWebFeeds { if feed.folderRelationship?.count ?? 0 > 1 { @@ -336,7 +336,7 @@ final class FeedbinAccountDelegate: AccountDelegate { switch result { case .success: DispatchQueue.main.async { - account.clearFeedMetadata(feed) + account.clearWebFeedMetadata(feed) } case .failure(let error): os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription) @@ -356,7 +356,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(1) caller.createSubscription(url: url) { result in @@ -388,7 +388,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { + func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { // This error should never happen guard let subscriptionID = feed.subscriptionID else { @@ -415,7 +415,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { + func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { if feed.folderRelationship?.count ?? 0 > 1 { deleteTagging(for: account, with: feed, from: container, completion: completion) } else { @@ -423,14 +423,14 @@ final class FeedbinAccountDelegate: AccountDelegate { } } - func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { if from is Account { - addFeed(for: account, with: feed, to: to, completion: completion) + addWebFeed(for: account, with: feed, to: to, completion: completion) } else { deleteTagging(for: account, with: feed, from: from) { result in switch result { case .success: - self.addFeed(for: account, with: feed, to: to, completion: completion) + self.addWebFeed(for: account, with: feed, to: to, completion: completion) case .failure(let error): completion(.failure(error)) } @@ -438,18 +438,18 @@ final class FeedbinAccountDelegate: AccountDelegate { } } - func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { + func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { - if let folder = container as? Folder, let feedID = Int(feed.feedID) { + if let folder = container as? Folder, let webFeedID = Int(feed.webFeedID) { refreshProgress.addToNumberOfTasksAndRemaining(1) - caller.createTagging(feedID: feedID, name: folder.name ?? "") { result in + caller.createTagging(webFeedID: webFeedID, name: folder.name ?? "") { result in self.refreshProgress.completeTask() switch result { case .success(let taggingID): DispatchQueue.main.async { self.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: String(taggingID)) - account.removeFeed(feed) - folder.addFeed(feed) + account.removeWebFeed(feed) + folder.addWebFeed(feed) completion(.success(())) } case .failure(let error): @@ -470,10 +470,10 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { + func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { - if let existingFeed = account.existingFeed(withURL: feed.url) { - account.addFeed(existingFeed, to: container) { result in + if let existingFeed = account.existingWebFeed(withURL: feed.url) { + account.addWebFeed(existingFeed, to: container) { result in switch result { case .success: completion(.success(())) @@ -482,7 +482,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } } } else { - createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in + createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in switch result { case .success: completion(.success(())) @@ -498,12 +498,12 @@ final class FeedbinAccountDelegate: AccountDelegate { let group = DispatchGroup() - for feed in folder.topLevelFeeds { + for feed in folder.topLevelWebFeeds { - folder.topLevelFeeds.remove(feed) + folder.topLevelWebFeeds.remove(feed) group.enter() - restoreFeed(for: account, feed: feed, container: folder) { result in + restoreWebFeed(for: account, feed: feed, container: folder) { result in group.leave() switch result { case .success: @@ -721,8 +721,8 @@ private extension FeedbinAccountDelegate { if let folders = account.folders { folders.forEach { folder in if !tagNames.contains(folder.name ?? "") { - for feed in folder.topLevelFeeds { - account.addFeed(feed) + for feed in folder.topLevelWebFeeds { + account.addWebFeed(feed) clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") } account.removeFolder(folder) @@ -759,17 +759,17 @@ private extension FeedbinAccountDelegate { // Remove any feeds that are no longer in the subscriptions if let folders = account.folders { for folder in folders { - for feed in folder.topLevelFeeds { - if !subFeedIds.contains(feed.feedID) { - folder.removeFeed(feed) + for feed in folder.topLevelWebFeeds { + if !subFeedIds.contains(feed.webFeedID) { + folder.removeWebFeed(feed) } } } } - for feed in account.topLevelFeeds { - if !subFeedIds.contains(feed.feedID) { - account.removeFeed(feed) + for feed in account.topLevelWebFeeds { + if !subFeedIds.contains(feed.webFeedID) { + account.removeWebFeed(feed) } } @@ -779,7 +779,7 @@ private extension FeedbinAccountDelegate { let subFeedId = String(subscription.feedID) - if let feed = account.existingFeed(withFeedID: subFeedId) { + if let feed = account.existingWebFeed(withWebFeedID: subFeedId) { feed.name = subscription.name // If the name has been changed on the server remove the locally edited name feed.editedName = nil @@ -795,9 +795,9 @@ private extension FeedbinAccountDelegate { // Actually add subscriptions all in one go, so we don’t trigger various rebuilding things that Account does. subscriptionsToAdd.forEach { subscription in - let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: String(subscription.feedID), homePageURL: subscription.homePageURL) + let feed = account.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: String(subscription.feedID), homePageURL: subscription.homePageURL) feed.subscriptionID = String(subscription.subscriptionID) - account.addFeed(feed) + account.addWebFeed(feed) } } @@ -836,25 +836,25 @@ private extension FeedbinAccountDelegate { let taggingFeedIDs = groupedTaggings.map { String($0.feedID) } // Move any feeds not in the folder to the account - for feed in folder.topLevelFeeds { - if !taggingFeedIDs.contains(feed.feedID) { - folder.removeFeed(feed) + for feed in folder.topLevelWebFeeds { + if !taggingFeedIDs.contains(feed.webFeedID) { + folder.removeWebFeed(feed) clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") - account.addFeed(feed) + account.addWebFeed(feed) } } // Add any feeds not in the folder - let folderFeedIds = folder.topLevelFeeds.map { $0.feedID } + let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID } for tagging in groupedTaggings { let taggingFeedID = String(tagging.feedID) if !folderFeedIds.contains(taggingFeedID) { - guard let feed = account.existingFeed(withFeedID: taggingFeedID) else { + guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else { continue } saveFolderRelationship(for: feed, withFolderName: folderName, id: String(tagging.taggingID)) - folder.addFeed(feed) + folder.addWebFeed(feed) } } @@ -863,9 +863,9 @@ private extension FeedbinAccountDelegate { let taggedFeedIDs = Set(taggings.map { String($0.feedID) }) // Remove all feeds from the account container that have a tag - for feed in account.topLevelFeeds { - if taggedFeedIDs.contains(feed.feedID) { - account.removeFeed(feed) + for feed in account.topLevelWebFeeds { + if taggedFeedIDs.contains(feed.webFeedID) { + account.removeWebFeed(feed) } } } @@ -913,7 +913,7 @@ private extension FeedbinAccountDelegate { } func renameFolderRelationship(for account: Account, fromName: String, toName: String) { - for feed in account.flattenedFeeds() { + for feed in account.flattenedWebFeeds() { if var folderRelationship = feed.folderRelationship { let relationship = folderRelationship[fromName] folderRelationship[fromName] = nil @@ -923,14 +923,14 @@ private extension FeedbinAccountDelegate { } } - func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) { + func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) { if var folderRelationship = feed.folderRelationship { folderRelationship[folderName] = nil feed.folderRelationship = folderRelationship } } - func saveFolderRelationship(for feed: Feed, withFolderName folderName: String, id: String) { + func saveFolderRelationship(for feed: WebFeed, withFolderName folderName: String, id: String) { if var folderRelationship = feed.folderRelationship { folderRelationship[folderName] = id feed.folderRelationship = folderRelationship @@ -939,7 +939,7 @@ private extension FeedbinAccountDelegate { } } - func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result) -> Void) { + func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result) -> Void) { let feedSpecifiers: [FeedSpecifier] = choices.map { choice in let source = url == choice.url ? FeedSpecifier.Source.UserEntered : FeedSpecifier.Source.HTMLLink @@ -949,7 +949,7 @@ private extension FeedbinAccountDelegate { if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) { if let bestSubscription = choices.filter({ bestSpecifier.urlString == $0.url }).first { - createFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion) + createWebFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion) } else { DispatchQueue.main.async { completion(.failure(FeedbinAccountDelegateError.invalidParameter)) @@ -963,20 +963,20 @@ private extension FeedbinAccountDelegate { } - func createFeed( account: Account, subscription sub: FeedbinSubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createFeed( account: Account, subscription sub: FeedbinSubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { DispatchQueue.main.async { - let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL) + let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL) feed.subscriptionID = String(sub.subscriptionID) feed.iconURL = sub.jsonFeed?.icon feed.faviconURL = sub.jsonFeed?.favicon - account.addFeed(feed, to: container) { result in + account.addWebFeed(feed, to: container) { result in switch result { case .success: if let name = name { - account.renameFeed(feed, to: name) { result in + account.renameWebFeed(feed, to: name) { result in switch result { case .success: self.initialFeedDownload(account: account, feed: feed, completion: completion) @@ -996,13 +996,13 @@ private extension FeedbinAccountDelegate { } - func initialFeedDownload( account: Account, feed: Feed, completion: @escaping (Result) -> Void) { + func initialFeedDownload( account: Account, feed: WebFeed, completion: @escaping (Result) -> Void) { // refreshArticles is being reused and will clear one of the tasks for us refreshProgress.addToNumberOfTasksAndRemaining(4) // Download the initial articles - self.caller.retrieveEntries(feedID: feed.feedID) { result in + self.caller.retrieveEntries(feedID: feed.webFeedID) { result in self.refreshProgress.completeTask() switch result { @@ -1155,8 +1155,8 @@ private extension FeedbinAccountDelegate { func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping (() -> Void)) { let parsedItems = mapEntriesToParsedItems(entries: entries) - let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) } - account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion) + let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) } + account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion) } func mapEntriesToParsedItems(entries: [FeedbinEntry]?) -> Set { @@ -1232,7 +1232,7 @@ private extension FeedbinAccountDelegate { account.ensureStatuses(missingUnstarredArticleIDs, true, .starred, false) } - func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result) -> Void) { + func deleteTagging(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result) -> Void) { if let folder = container as? Folder, let feedTaggingID = feed.folderRelationship?[folder.name ?? ""] { refreshProgress.addToNumberOfTasksAndRemaining(1) @@ -1242,7 +1242,7 @@ private extension FeedbinAccountDelegate { case .success: DispatchQueue.main.async { self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") - folder.removeFeed(feed) + folder.removeWebFeed(feed) account.addFeedIfNotInAnyFolder(feed) completion(.success(())) } @@ -1255,14 +1255,14 @@ private extension FeedbinAccountDelegate { } } else { if let account = container as? Account { - account.removeFeed(feed) + account.removeWebFeed(feed) } completion(.success(())) } } - func deleteSubscription(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result) -> Void) { + func deleteSubscription(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result) -> Void) { // This error should never happen guard let subscriptionID = feed.subscriptionID else { @@ -1276,11 +1276,11 @@ private extension FeedbinAccountDelegate { switch result { case .success: DispatchQueue.main.async { - account.clearFeedMetadata(feed) - account.removeFeed(feed) + account.clearWebFeedMetadata(feed) + account.removeWebFeed(feed) if let folders = account.folders { for folder in folders { - folder.removeFeed(feed) + folder.removeWebFeed(feed) } } completion(.success(())) diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index fbec23b02..453e9df7f 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -294,7 +294,7 @@ final class FeedlyAccountDelegate: AccountDelegate { var createFeedRequest: FeedlyAddFeedRequest? - func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { let progress = refreshProgress progress.addToNumberOfTasksAndRemaining(1) @@ -310,14 +310,14 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { + func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { let folderCollectionIds = account.folders?.filter { $0.has(feed) }.compactMap { $0.externalID } guard let collectionIds = folderCollectionIds, let collectionId = collectionIds.first else { completion(.failure(FeedlyAccountDelegateError.unableToRenameFeed(feed.nameForDisplay, name))) return } - let feedId = FeedlyFeedResourceId(id: feed.feedID) + let feedId = FeedlyFeedResourceId(id: feed.webFeedID) let editedNameBefore = feed.editedName // Adding an existing feed updates it. @@ -339,7 +339,7 @@ final class FeedlyAccountDelegate: AccountDelegate { var addFeedRequest: FeedlyAddFeedRequest? - func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { + func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { let progress = refreshProgress progress.addToNumberOfTasksAndRemaining(1) @@ -362,62 +362,62 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { + func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { guard let folder = container as? Folder, let collectionId = folder.externalID else { return DispatchQueue.main.async { completion(.failure(FeedlyAccountDelegateError.unableToRemoveFeed(feed))) } } - caller.removeFeed(feed.feedID, fromCollectionWith: collectionId) { result in + caller.removeFeed(feed.webFeedID, fromCollectionWith: collectionId) { result in switch result { case .success: completion(.success(())) case .failure(let error): - folder.addFeed(feed) + folder.addWebFeed(feed) completion(.failure(error)) } } - folder.removeFeed(feed) + folder.removeWebFeed(feed) } - func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { guard let from = from as? Folder, let to = to as? Folder else { return DispatchQueue.main.async { completion(.failure(FeedlyAccountDelegateError.addFeedChooseFolder)) } } - addFeed(for: account, with: feed, to: to) { [weak self] addResult in + addWebFeed(for: account, with: feed, to: to) { [weak self] addResult in switch addResult { // now that we have added the feed, remove it from the other collection case .success: - self?.removeFeed(for: account, with: feed, from: from) { removeResult in + self?.removeWebFeed(for: account, with: feed, from: from) { removeResult in switch removeResult { case .success: completion(.success(())) case .failure: - from.addFeed(feed) + from.addWebFeed(feed) completion(.failure(FeedlyAccountDelegateError.unableToMoveFeedBetweenFolders(feed, from, to))) } } case .failure(let error): - from.addFeed(feed) - to.removeFeed(feed) + from.addWebFeed(feed) + to.removeWebFeed(feed) completion(.failure(error)) } } // optimistically move the feed, undoing as appropriate to the failure - from.removeFeed(feed) - to.addFeed(feed) + from.removeWebFeed(feed) + to.addWebFeed(feed) } - func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { - if let existingFeed = account.existingFeed(withURL: feed.url) { - account.addFeed(existingFeed, to: container) { result in + func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + if let existingFeed = account.existingWebFeed(withURL: feed.url) { + account.addWebFeed(existingFeed, to: container) { result in switch result { case .success: completion(.success(())) @@ -426,7 +426,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } } } else { - createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in + createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in switch result { case .success: completion(.success(())) @@ -440,12 +440,12 @@ final class FeedlyAccountDelegate: AccountDelegate { func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) { let group = DispatchGroup() - for feed in folder.topLevelFeeds { + for feed in folder.topLevelWebFeeds { - folder.topLevelFeeds.remove(feed) + folder.topLevelWebFeeds.remove(feed) group.enter() - restoreFeed(for: account, feed: feed, container: folder) { result in + restoreWebFeed(for: account, feed: feed, container: folder) { result in group.leave() switch result { case .success: diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegateError.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegateError.swift index a77fcfe02..578de4322 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegateError.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegateError.swift @@ -13,11 +13,11 @@ enum FeedlyAccountDelegateError: LocalizedError { case unableToAddFolder(String) case unableToRenameFolder(String, String) case unableToRemoveFolder(String) - case unableToMoveFeedBetweenFolders(Feed, Folder, Folder) + case unableToMoveFeedBetweenFolders(WebFeed, Folder, Folder) case addFeedChooseFolder case addFeedInvalidFolder(Folder) case unableToRenameFeed(String, String) - case unableToRemoveFeed(Feed) + case unableToRemoveFeed(WebFeed) var errorDescription: String? { switch self { diff --git a/Frameworks/Account/Feedly/FeedlyAddFeedRequest.swift b/Frameworks/Account/Feedly/FeedlyAddFeedRequest.swift index 66aecf729..e104d5312 100644 --- a/Frameworks/Account/Feedly/FeedlyAddFeedRequest.swift +++ b/Frameworks/Account/Feedly/FeedlyAddFeedRequest.swift @@ -29,7 +29,7 @@ final class FeedlyAddFeedRequest { self.resourceProvider = resourceProvider } - var completionHandler: ((Result) -> ())? + var completionHandler: ((Result) -> ())? var error: Error? func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) { @@ -37,17 +37,17 @@ final class FeedlyAddFeedRequest { } } - func addNewFeed(at url: String, name: String? = nil, completion: @escaping (Result) -> Void) { + func addNewFeed(at url: String, name: String? = nil, completion: @escaping (Result) -> Void) { let resource = FeedlyFeedResourceId(url: url) self.start(resource: resource, name: name, refreshes: true, completion: completion) } - func add(existing feed: Feed, name: String? = nil, completion: @escaping (Result) -> Void) { - let resource = FeedlyFeedResourceId(id: feed.feedID) + func add(existing feed: WebFeed, name: String? = nil, completion: @escaping (Result) -> Void) { + let resource = FeedlyFeedResourceId(id: feed.webFeedID) self.start(resource: resource, name: name, refreshes: false, completion: completion) } - private func start(resource: FeedlyFeedResourceId, name: String?, refreshes: Bool, completion: @escaping (Result) -> Void) { + private func start(resource: FeedlyFeedResourceId, name: String?, refreshes: Bool, completion: @escaping (Result) -> Void) { let (folder, collectionId): (Folder, String) do { @@ -115,7 +115,7 @@ final class FeedlyAddFeedRequest { if let error = delegate.error { handler(.failure(error)) - } else if let feed = folder.existingFeed(withFeedID: resource.id) { + } else if let feed = folder.existingWebFeed(withWebFeedID: resource.id) { handler(.success(feed)) } else { diff --git a/Frameworks/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift index b5d8cba92..990c05bcf 100644 --- a/Frameworks/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift +++ b/Frameworks/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift @@ -31,13 +31,13 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { let feedsBefore = Set(pairs .map { $0.1 } - .flatMap { $0.topLevelFeeds }) + .flatMap { $0.topLevelWebFeeds }) // Remove feeds in a folder which are not in the corresponding collection. for (collectionFeeds, folder) in pairs { - let feedsInFolder = folder.topLevelFeeds + let feedsInFolder = folder.topLevelWebFeeds let feedsInCollection = Set(collectionFeeds.map { $0.id }) - let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.feedID) } + let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.webFeedID) } if !feedsToRemove.isEmpty { folder.removeFeeds(feedsToRemove) // os_log(.debug, log: log, "\"%@\" - removed: %@", collection.label, feedsToRemove.map { $0.feedID }, feedsInCollection) @@ -46,7 +46,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { } // Pair each Feed with its Folder. - var feedsAdded = Set() + var feedsAdded = Set() let feedsAndFolders = pairs .map({ (collectionFeeds, folder) -> [(FeedlyFeed, Folder)] in @@ -55,14 +55,14 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { } }) .flatMap { $0 } - .compactMap { (collectionFeed, folder) -> (Feed, Folder) in + .compactMap { (collectionFeed, folder) -> (WebFeed, Folder) in // find an existing feed previously added to the account - if let feed = account.existingFeed(withFeedID: collectionFeed.id) { + if let feed = account.existingWebFeed(withWebFeedID: collectionFeed.id) { return (feed, folder) } else { // find an existing feed we created below in an earlier value - for feed in feedsAdded where feed.feedID == collectionFeed.id { + for feed in feedsAdded where feed.webFeedID == collectionFeed.id { return (feed, folder) } } @@ -70,7 +70,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { // no exsiting feed, create a new one let id = collectionFeed.id let url = FeedlyFeedResourceId(id: id).url - let feed = account.createFeed(with: collectionFeed.title, url: url, feedID: id, homePageURL: collectionFeed.website) + let feed = account.createWebFeed(with: collectionFeed.title, url: url, webFeedID: id, homePageURL: collectionFeed.website) // So the same feed isn't created more than once. feedsAdded.insert(feed) @@ -81,7 +81,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { os_log(.debug, log: log, "Processing %i feeds.", feedsAndFolders.count) feedsAndFolders.forEach { (feed, folder) in if !folder.has(feed) { - folder.addFeed(feed) + folder.addWebFeed(feed) } } diff --git a/Frameworks/Account/Feedly/Operations/FeedlyUpdateAccountFeedsWithItemsOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlyUpdateAccountFeedsWithItemsOperation.swift index e6620a61a..850f24c28 100644 --- a/Frameworks/Account/Feedly/Operations/FeedlyUpdateAccountFeedsWithItemsOperation.swift +++ b/Frameworks/Account/Feedly/Operations/FeedlyUpdateAccountFeedsWithItemsOperation.swift @@ -29,10 +29,10 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation { return } - let feedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId + let webFeedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId - account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { - os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", feedIDsAndItems.count, self.organisedItemsProvider.providerName) + account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { + os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", webFeedIDsAndItems.count, self.organisedItemsProvider.providerName) self.didFinish() } } diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index 28eeddd98..47b4292b6 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -13,7 +13,7 @@ import RSCore public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCountProvider, Hashable { public weak var account: Account? - public var topLevelFeeds: Set = Set() + public var topLevelWebFeeds: Set = Set() public var folders: Set? = nil // subfolders are not supported, so this is always nil public var name: String? { @@ -80,42 +80,42 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun // MARK: Container - public func flattenedFeeds() -> Set { + public func flattenedWebFeeds() -> Set { // Since sub-folders are not supported, it’s always the top-level feeds. - return topLevelFeeds + return topLevelWebFeeds } public func objectIsChild(_ object: AnyObject) -> Bool { // Folders contain Feed objects only, at least for now. - guard let feed = object as? Feed else { + guard let feed = object as? WebFeed else { return false } - return topLevelFeeds.contains(feed) + return topLevelWebFeeds.contains(feed) } - public func addFeed(_ feed: Feed) { - topLevelFeeds.insert(feed) + public func addWebFeed(_ feed: WebFeed) { + topLevelWebFeeds.insert(feed) postChildrenDidChangeNotification() } - public func addFeeds(_ feeds: Set) { + public func addFeeds(_ feeds: Set) { guard !feeds.isEmpty else { return } - topLevelFeeds.formUnion(feeds) + topLevelWebFeeds.formUnion(feeds) postChildrenDidChangeNotification() } - public func removeFeed(_ feed: Feed) { - topLevelFeeds.remove(feed) + public func removeWebFeed(_ feed: WebFeed) { + topLevelWebFeeds.remove(feed) postChildrenDidChangeNotification() } - public func removeFeeds(_ feeds: Set) { + public func removeFeeds(_ feeds: Set) { guard !feeds.isEmpty else { return } - topLevelFeeds.subtract(feeds) + topLevelWebFeeds.subtract(feeds) postChildrenDidChangeNotification() } @@ -138,14 +138,14 @@ private extension Folder { func updateUnreadCount() { var updatedUnreadCount = 0 - for feed in topLevelFeeds { + for feed in topLevelWebFeeds { updatedUnreadCount += feed.unreadCount } unreadCount = updatedUnreadCount } - func childrenContain(_ feed: Feed) -> Bool { - return topLevelFeeds.contains(feed) + func childrenContain(_ feed: WebFeed) -> Bool { + return topLevelWebFeeds.contains(feed) } } @@ -169,7 +169,7 @@ extension Folder: OPMLRepresentable { var hasAtLeastOneChild = false - for feed in topLevelFeeds { + for feed in topLevelWebFeeds { s += feed.OPMLString(indentLevel: indentLevel + 1, strictConformance: strictConformance) hasAtLeastOneChild = true } diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 25a62e74c..5cbfdad9f 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -36,7 +36,7 @@ final class LocalAccountDelegate: AccountDelegate { } func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { - refresher.refreshFeeds(account.flattenedFeeds()) { + refresher.refreshFeeds(account.flattenedWebFeeds()) { completion(.success(())) } } @@ -91,7 +91,7 @@ final class LocalAccountDelegate: AccountDelegate { } - func createFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { guard let url = URL(string: urlString) else { completion(.failure(LocalAccountDelegateError.invalidParameter)) return @@ -109,13 +109,13 @@ final class LocalAccountDelegate: AccountDelegate { return } - if account.hasFeed(withURL: bestFeedSpecifier.urlString) { + if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) { self.refreshProgress.completeTask() completion(.failure(AccountError.createErrorAlreadySubscribed)) return } - let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil) + let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) InitialFeedDownloader.download(url) { parsedFeed in self.refreshProgress.completeTask() @@ -126,7 +126,7 @@ final class LocalAccountDelegate: AccountDelegate { feed.editedName = name - container.addFeed(feed) + container.addWebFeed(feed) completion(.success(feed)) } @@ -140,29 +140,29 @@ final class LocalAccountDelegate: AccountDelegate { } - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { + func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { feed.editedName = name completion(.success(())) } - func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { - container.removeFeed(feed) + func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + container.removeWebFeed(feed) completion(.success(())) } - func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { - from.removeFeed(feed) - to.addFeed(feed) + func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + from.removeWebFeed(feed) + to.addWebFeed(feed) completion(.success(())) } - func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { - container.addFeed(feed) + func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { + container.addWebFeed(feed) completion(.success(())) } - func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { - container.addFeed(feed) + func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + container.addWebFeed(feed) completion(.success(())) } diff --git a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift index b5ad81d68..da9d21b69 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift @@ -24,7 +24,7 @@ final class LocalAccountRefresher { return downloadSession.progress } - public func refreshFeeds(_ feeds: Set, completion: @escaping () -> Void) { + public func refreshFeeds(_ feeds: Set, completion: @escaping () -> Void) { self.completion = completion downloadSession.downloadObjects(feeds as NSSet) } @@ -40,7 +40,7 @@ final class LocalAccountRefresher { extension LocalAccountRefresher: DownloadSessionDelegate { func downloadSession(_ downloadSession: DownloadSession, requestForRepresentedObject representedObject: AnyObject) -> URLRequest? { - guard let feed = representedObject as? Feed else { + guard let feed = representedObject as? WebFeed else { return nil } guard let url = URL(string: feed.url) else { @@ -56,7 +56,7 @@ extension LocalAccountRefresher: DownloadSessionDelegate { } func downloadSession(_ downloadSession: DownloadSession, downloadDidCompleteForRepresentedObject representedObject: AnyObject, response: URLResponse?, data: Data, error: NSError?) { - guard let feed = representedObject as? Feed, !data.isEmpty else { + guard let feed = representedObject as? WebFeed, !data.isEmpty else { return } @@ -86,7 +86,7 @@ extension LocalAccountRefresher: DownloadSessionDelegate { } func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool { - guard let feed = representedObject as? Feed else { + guard let feed = representedObject as? WebFeed else { return false } diff --git a/Frameworks/Account/OPMLFile.swift b/Frameworks/Account/OPMLFile.swift index d63a6f2a0..ef4b85e03 100644 --- a/Frameworks/Account/OPMLFile.swift +++ b/Frameworks/Account/OPMLFile.swift @@ -43,7 +43,7 @@ private extension OPMLFile { func loadCallback() { guard let opmlItems = parsedOPMLItems() else { return } BatchUpdate.shared.perform { - account.topLevelFeeds.removeAll() + account.topLevelWebFeeds.removeAll() account.loadOPMLItems(opmlItems, parentFolder: nil) } } diff --git a/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 4de3ff830..c127a91c6 100644 --- a/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -227,9 +227,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate { func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { let group = DispatchGroup() - for feed in folder.topLevelFeeds { + for feed in folder.topLevelWebFeeds { group.enter() - removeFeed(for: account, with: feed, from: folder) { result in + removeWebFeed(for: account, with: feed, from: folder) { result in group.leave() switch result { case .success: @@ -256,7 +256,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } - func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { caller.createSubscription(url: url) { result in switch result { @@ -284,7 +284,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { + func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { // This error should never happen guard let subscriptionID = feed.subscriptionID else { @@ -309,23 +309,23 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } - func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { + func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { if feed.folderRelationship?.count ?? 0 > 1 { deleteTagging(for: account, with: feed, from: container, completion: completion) } else { - account.clearFeedMetadata(feed) + account.clearWebFeedMetadata(feed) deleteSubscription(for: account, with: feed, from: container, completion: completion) } } - func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { if from is Account { - addFeed(for: account, with: feed, to: to, completion: completion) + addWebFeed(for: account, with: feed, to: to, completion: completion) } else { deleteTagging(for: account, with: feed, from: from) { result in switch result { case .success: - self.addFeed(for: account, with: feed, to: to, completion: completion) + self.addWebFeed(for: account, with: feed, to: to, completion: completion) case .failure(let error): completion(.failure(error)) } @@ -333,7 +333,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } } - func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { + func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { if let folder = container as? Folder, let feedName = feed.subscriptionID { caller.createTagging(subscriptionID: feedName, tagName: folder.name ?? "") { result in @@ -341,8 +341,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate { case .success: DispatchQueue.main.async { self.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: feed.subscriptionID!) - account.removeFeed(feed) - folder.addFeed(feed) + account.removeWebFeed(feed) + folder.addWebFeed(feed) completion(.success(())) } case .failure(let error): @@ -363,9 +363,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } - func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { + func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { - createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in + createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in switch result { case .success: completion(.success(())) @@ -381,12 +381,12 @@ final class ReaderAPIAccountDelegate: AccountDelegate { account.addFolder(folder) let group = DispatchGroup() - for feed in folder.topLevelFeeds { + for feed in folder.topLevelWebFeeds { group.enter() - addFeed(for: account, with: feed, to: folder) { result in - if account.topLevelFeeds.contains(feed) { - account.removeFeed(feed) + addWebFeed(for: account, with: feed, to: folder) { result in + if account.topLevelWebFeeds.contains(feed) { + account.removeWebFeed(feed) } group.leave() } @@ -469,8 +469,8 @@ private extension ReaderAPIAccountDelegate { if let folders = account.folders { folders.forEach { folder in if !tagNames.contains(folder.name ?? "") { - for feed in folder.topLevelFeeds { - account.addFeed(feed) + for feed in folder.topLevelWebFeeds { + account.addWebFeed(feed) clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") } account.removeFolder(folder) @@ -532,17 +532,17 @@ private extension ReaderAPIAccountDelegate { // Remove any feeds that are no longer in the subscriptions if let folders = account.folders { for folder in folders { - for feed in folder.topLevelFeeds { - if !subFeedIds.contains(feed.feedID) { - folder.removeFeed(feed) + for feed in folder.topLevelWebFeeds { + if !subFeedIds.contains(feed.webFeedID) { + folder.removeWebFeed(feed) } } } } - for feed in account.topLevelFeeds { - if !subFeedIds.contains(feed.feedID) { - account.removeFeed(feed) + for feed in account.topLevelWebFeeds { + if !subFeedIds.contains(feed.webFeedID) { + account.removeWebFeed(feed) } } @@ -550,14 +550,14 @@ private extension ReaderAPIAccountDelegate { subscriptions.forEach { subscription in let subFeedId = String(subscription.feedID) - if let feed = account.existingFeed(withFeedID: subFeedId) { + if let feed = account.existingWebFeed(withWebFeedID: subFeedId) { feed.name = subscription.name feed.homePageURL = subscription.homePageURL } else { - let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: subFeedId, homePageURL: subscription.homePageURL) + let feed = account.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: subFeedId, homePageURL: subscription.homePageURL) feed.iconURL = subscription.iconURL feed.subscriptionID = String(subscription.feedID) - account.addFeed(feed) + account.addWebFeed(feed) } } @@ -606,25 +606,25 @@ private extension ReaderAPIAccountDelegate { let taggingFeedIDs = groupedTaggings.map { String($0.feedID) } // Move any feeds not in the folder to the account - for feed in folder.topLevelFeeds { - if !taggingFeedIDs.contains(feed.feedID) { - folder.removeFeed(feed) + for feed in folder.topLevelWebFeeds { + if !taggingFeedIDs.contains(feed.webFeedID) { + folder.removeWebFeed(feed) clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") - account.addFeed(feed) + account.addWebFeed(feed) } } // Add any feeds not in the folder - let folderFeedIds = folder.topLevelFeeds.map { $0.feedID } + let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID } for subscription in groupedTaggings { let taggingFeedID = String(subscription.feedID) if !folderFeedIds.contains(taggingFeedID) { - guard let feed = account.existingFeed(withFeedID: taggingFeedID) else { + guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else { continue } saveFolderRelationship(for: feed, withFolderName: folderName, id: String(subscription.feedID)) - folder.addFeed(feed) + folder.addWebFeed(feed) } } @@ -633,9 +633,9 @@ private extension ReaderAPIAccountDelegate { let taggedFeedIDs = Set(subscriptions.map { String($0.feedID) }) // Remove all feeds from the account container that have a tag - for feed in account.topLevelFeeds { - if taggedFeedIDs.contains(feed.feedID) { - account.removeFeed(feed) + for feed in account.topLevelWebFeeds { + if taggedFeedIDs.contains(feed.webFeedID) { + account.removeWebFeed(feed) } } @@ -679,14 +679,14 @@ private extension ReaderAPIAccountDelegate { - func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) { + func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) { if var folderRelationship = feed.folderRelationship { folderRelationship[folderName] = nil feed.folderRelationship = folderRelationship } } - func saveFolderRelationship(for feed: Feed, withFolderName folderName: String, id: String) { + func saveFolderRelationship(for feed: WebFeed, withFolderName folderName: String, id: String) { if var folderRelationship = feed.folderRelationship { folderRelationship[folderName] = id feed.folderRelationship = folderRelationship @@ -695,7 +695,7 @@ private extension ReaderAPIAccountDelegate { } } - func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [ReaderAPISubscriptionChoice], completion: @escaping (Result) -> Void) { + func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [ReaderAPISubscriptionChoice], completion: @escaping (Result) -> Void) { let feedSpecifiers: [FeedSpecifier] = choices.map { choice in let source = url == choice.url ? FeedSpecifier.Source.UserEntered : FeedSpecifier.Source.HTMLLink @@ -705,7 +705,7 @@ private extension ReaderAPIAccountDelegate { if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) { if let bestSubscription = choices.filter({ bestSpecifier.urlString == $0.url }).first { - createFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion) + createWebFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion) } else { DispatchQueue.main.async { completion(.failure(ReaderAPIAccountDelegateError.invalidParameter)) @@ -719,18 +719,18 @@ private extension ReaderAPIAccountDelegate { } - func createFeed( account: Account, subscription sub: ReaderAPISubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createFeed( account: Account, subscription sub: ReaderAPISubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { DispatchQueue.main.async { - let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL) + let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL) feed.subscriptionID = String(sub.feedID) - account.addFeed(feed, to: container) { result in + account.addWebFeed(feed, to: container) { result in switch result { case .success: if let name = name { - account.renameFeed(feed, to: name) { result in + account.renameWebFeed(feed, to: name) { result in switch result { case .success: self.initialFeedDownload(account: account, feed: feed, completion: completion) @@ -750,10 +750,10 @@ private extension ReaderAPIAccountDelegate { } - func initialFeedDownload( account: Account, feed: Feed, completion: @escaping (Result) -> Void) { + func initialFeedDownload( account: Account, feed: WebFeed, completion: @escaping (Result) -> Void) { // Download the initial articles - self.caller.retrieveEntries(feedID: feed.feedID) { result in + self.caller.retrieveEntries(webFeedID: feed.webFeedID) { result in switch result { case .success(let (entries, page)): @@ -871,8 +871,8 @@ private extension ReaderAPIAccountDelegate { func processEntries(account: Account, entries: [ReaderAPIEntry]?, completion: @escaping (() -> Void)) { let parsedItems = mapEntriesToParsedItems(account: account, entries: entries) - let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) } - account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion) + let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) } + account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion) } func mapEntriesToParsedItems(account: Account, entries: [ReaderAPIEntry]?) -> Set { @@ -953,7 +953,7 @@ private extension ReaderAPIAccountDelegate { - func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result) -> Void) { + func deleteTagging(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result) -> Void) { if let folder = container as? Folder, let feedName = feed.subscriptionID { caller.deleteTagging(subscriptionID: feedName, tagName: folder.name ?? "") { result in @@ -961,7 +961,7 @@ private extension ReaderAPIAccountDelegate { case .success: DispatchQueue.main.async { self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") - folder.removeFeed(feed) + folder.removeWebFeed(feed) account.addFeedIfNotInAnyFolder(feed) completion(.success(())) } @@ -974,14 +974,14 @@ private extension ReaderAPIAccountDelegate { } } else { if let account = container as? Account { - account.removeFeed(feed) + account.removeWebFeed(feed) } completion(.success(())) } } - func deleteSubscription(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result) -> Void) { + func deleteSubscription(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result) -> Void) { // This error should never happen guard let subscriptionID = feed.subscriptionID else { @@ -993,10 +993,10 @@ private extension ReaderAPIAccountDelegate { switch result { case .success: DispatchQueue.main.async { - account.removeFeed(feed) + account.removeWebFeed(feed) if let folders = account.folders { for folder in folders { - folder.removeFeed(feed) + folder.removeWebFeed(feed) } } completion(.success(())) diff --git a/Frameworks/Account/ReaderAPI/ReaderAPICaller.swift b/Frameworks/Account/ReaderAPI/ReaderAPICaller.swift index 1b9454856..e37bb211f 100644 --- a/Frameworks/Account/ReaderAPI/ReaderAPICaller.swift +++ b/Frameworks/Account/ReaderAPI/ReaderAPICaller.swift @@ -594,7 +594,7 @@ final class ReaderAPICaller: NSObject { } - func retrieveEntries(feedID: String, completion: @escaping (Result<([ReaderAPIEntry]?, String?), Error>) -> Void) { + func retrieveEntries(webFeedID: String, completion: @escaping (Result<([ReaderAPIEntry]?, String?), Error>) -> Void) { let since = Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date() @@ -606,7 +606,7 @@ final class ReaderAPICaller: NSObject { let url = baseURL .appendingPathComponent(ReaderAPIEndpoints.itemIds.rawValue) .appendingQueryItems([ - URLQueryItem(name: "s", value: feedID), + URLQueryItem(name: "s", value: webFeedID), URLQueryItem(name: "ot", value: String(since.timeIntervalSince1970)), URLQueryItem(name: "output", value: "json") ]) diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/WebFeed.swift similarity index 88% rename from Frameworks/Account/Feed.swift rename to Frameworks/Account/WebFeed.swift index c2855ef30..1ceb79bdc 100644 --- a/Frameworks/Account/Feed.swift +++ b/Frameworks/Account/WebFeed.swift @@ -1,5 +1,5 @@ // -// Feed.swift +// WebFeed.swift // NetNewsWire // // Created by Brent Simmons on 7/1/17. @@ -11,17 +11,17 @@ import RSCore import RSWeb import Articles -public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Hashable { +public final class WebFeed: DisplayNameProvider, Renamable, UnreadCountProvider, Hashable { public weak var account: Account? public let url: String - public var feedID: String { + public var webFeedID: String { get { - return metadata.feedID + return metadata.webFeedID } set { - metadata.feedID = newValue + metadata.webFeedID = newValue } } @@ -176,7 +176,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha public func rename(to newName: String, completion: @escaping (Result) -> Void) { guard let account = account else { return } - account.renameFeed(self, to: newName, completion: completion) + account.renameWebFeed(self, to: newName, completion: completion) } // MARK: - UnreadCountProvider @@ -194,7 +194,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha } } - var metadata: FeedMetadata + var metadata: WebFeedMetadata // MARK: - Private @@ -202,7 +202,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha // MARK: - Init - init(account: Account, url: String, metadata: FeedMetadata) { + init(account: Account, url: String, metadata: WebFeedMetadata) { self.account = account self.accountID = account.accountID self.url = url @@ -219,20 +219,20 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha // MARK: - Hashable public func hash(into hasher: inout Hasher) { - hasher.combine(feedID) + hasher.combine(webFeedID) hasher.combine(accountID) } // MARK: - Equatable - public class func ==(lhs: Feed, rhs: Feed) -> Bool { - return lhs.feedID == rhs.feedID && lhs.accountID == rhs.accountID + public class func ==(lhs: WebFeed, rhs: WebFeed) -> Bool { + return lhs.webFeedID == rhs.webFeedID && lhs.accountID == rhs.accountID } } // MARK: - OPMLRepresentable -extension Feed: OPMLRepresentable { +extension WebFeed: OPMLRepresentable { public func OPMLString(indentLevel: Int, strictConformance: Bool) -> String { // https://github.com/brentsimmons/NetNewsWire/issues/527 @@ -260,9 +260,9 @@ extension Feed: OPMLRepresentable { } } -extension Set where Element == Feed { +extension Set where Element == WebFeed { - func feedIDs() -> Set { - return Set(map { $0.feedID }) + func webFeedIDs() -> Set { + return Set(map { $0.webFeedID }) } } diff --git a/Frameworks/Account/FeedMetadata.swift b/Frameworks/Account/WebFeedMetadata.swift similarity index 83% rename from Frameworks/Account/FeedMetadata.swift rename to Frameworks/Account/WebFeedMetadata.swift index a85c259f2..8af4eaa6a 100644 --- a/Frameworks/Account/FeedMetadata.swift +++ b/Frameworks/Account/WebFeedMetadata.swift @@ -1,5 +1,5 @@ // -// FeedMetadata.swift +// WebFeedMetadata.swift // NetNewsWire // // Created by Brent Simmons on 3/12/19. @@ -10,14 +10,14 @@ import Foundation import RSWeb import Articles -protocol FeedMetadataDelegate: class { - func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) +protocol WebFeedMetadataDelegate: class { + func valueDidChange(_ feedMetadata: WebFeedMetadata, key: WebFeedMetadata.CodingKeys) } -final class FeedMetadata: Codable { +final class WebFeedMetadata: Codable { enum CodingKeys: String, CodingKey { - case feedID + case webFeedID = "feedID" case homePageURL case iconURL case faviconURL @@ -31,10 +31,10 @@ final class FeedMetadata: Codable { case folderRelationship } - var feedID: String { + var webFeedID: String { didSet { - if feedID != oldValue { - valueDidChange(.feedID) + if webFeedID != oldValue { + valueDidChange(.webFeedID) } } } @@ -128,10 +128,10 @@ final class FeedMetadata: Codable { } } - weak var delegate: FeedMetadataDelegate? + weak var delegate: WebFeedMetadataDelegate? - init(feedID: String) { - self.feedID = feedID + init(webFeedID: String) { + self.webFeedID = webFeedID } func valueDidChange(_ key: CodingKeys) { diff --git a/Frameworks/Account/FeedMetadataFile.swift b/Frameworks/Account/WebFeedMetadataFile.swift similarity index 74% rename from Frameworks/Account/FeedMetadataFile.swift rename to Frameworks/Account/WebFeedMetadataFile.swift index cfc900d83..3dfe594a3 100644 --- a/Frameworks/Account/FeedMetadataFile.swift +++ b/Frameworks/Account/WebFeedMetadataFile.swift @@ -1,5 +1,5 @@ // -// FeedMetadataFile.swift +// WebFeedMetadataFile.swift // Account // // Created by Maurice Parker on 9/13/19. @@ -10,9 +10,9 @@ import Foundation import os.log import RSCore -final class FeedMetadataFile { +final class WebFeedMetadataFile { - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "feedMetadataFile") + private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "webFeedMetadataFile") private let fileURL: URL private let account: Account @@ -37,7 +37,7 @@ final class FeedMetadataFile { } -private extension FeedMetadataFile { +private extension WebFeedMetadataFile { func loadCallback() { @@ -47,11 +47,11 @@ private extension FeedMetadataFile { fileCoordinator.coordinate(readingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { readURL in if let fileData = try? Data(contentsOf: readURL) { let decoder = PropertyListDecoder() - account.feedMetadata = (try? decoder.decode(Account.FeedMetadataDictionary.self, from: fileData)) ?? Account.FeedMetadataDictionary() + account.webFeedMetadata = (try? decoder.decode(Account.WebFeedMetadataDictionary.self, from: fileData)) ?? Account.WebFeedMetadataDictionary() } - account.feedMetadata.values.forEach { $0.delegate = account } + account.webFeedMetadata.values.forEach { $0.delegate = account } if !account.startingUp { - account.resetFeedMetadataAndUnreadCounts() + account.resetWebFeedMetadataAndUnreadCounts() } }) @@ -87,10 +87,10 @@ private extension FeedMetadataFile { } } - private func metadataForOnlySubscribedToFeeds() -> Account.FeedMetadataDictionary { - let feedIDs = account.idToFeedDictionary.keys - return account.feedMetadata.filter { (feedID: String, metadata: FeedMetadata) -> Bool in - return feedIDs.contains(metadata.feedID) + private func metadataForOnlySubscribedToFeeds() -> Account.WebFeedMetadataDictionary { + let webFeedIDs = account.idToWebFeedDictionary.keys + return account.webFeedMetadata.filter { (feedID: String, metadata: WebFeedMetadata) -> Bool in + return webFeedIDs.contains(metadata.webFeedID) } } diff --git a/Frameworks/Articles/Article.swift b/Frameworks/Articles/Article.swift index cd40d5cb6..8e11b72fd 100644 --- a/Frameworks/Articles/Article.swift +++ b/Frameworks/Articles/Article.swift @@ -14,7 +14,7 @@ public struct Article: Hashable { public let articleID: String // Unique database ID (possibly sync service ID) public let accountID: String - public let feedID: String // Likely a URL, but not necessarily + public let webFeedID: String // Likely a URL, but not necessarily public let uniqueID: String // Unique per feed (RSS guid, for example) public let title: String? public let contentHTML: String? @@ -30,10 +30,10 @@ public struct Article: Hashable { public let attachments: Set? public let status: ArticleStatus - public init(accountID: String, articleID: String?, feedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: Set?, attachments: Set?, status: ArticleStatus) { + public init(accountID: String, articleID: String?, webFeedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: Set?, attachments: Set?, status: ArticleStatus) { self.accountID = accountID - self.feedID = feedID + self.webFeedID = webFeedID self.uniqueID = uniqueID self.title = title self.contentHTML = contentHTML @@ -53,12 +53,12 @@ public struct Article: Hashable { self.articleID = articleID } else { - self.articleID = Article.calculatedArticleID(feedID: feedID, uniqueID: uniqueID) + self.articleID = Article.calculatedArticleID(webFeedID: webFeedID, uniqueID: uniqueID) } } - public static func calculatedArticleID(feedID: String, uniqueID: String) -> String { - return databaseIDWithString("\(feedID) \(uniqueID)") + public static func calculatedArticleID(webFeedID: String, uniqueID: String) -> String { + return databaseIDWithString("\(webFeedID) \(uniqueID)") } // MARK: - Hashable diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index 2c4bc08d1..cdc116511 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -16,7 +16,7 @@ import Articles // Main thread only. -public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount +public typealias UnreadCountDictionary = [String: Int] // webFeedID: unreadCount public typealias UnreadCountCompletionBlock = (UnreadCountDictionary) -> Void public typealias UpdateArticlesCompletionBlock = (Set
?, Set
?) -> Void //newArticles, updatedArticles @@ -44,28 +44,28 @@ public final class ArticlesDatabase { // MARK: - Fetching Articles - public func fetchArticles(_ feedID: String) -> Set
{ - return articlesTable.fetchArticles(feedID) + public func fetchArticles(_ webFeedID: String) -> Set
{ + return articlesTable.fetchArticles(webFeedID) } public func fetchArticles(articleIDs: Set) -> Set
{ return articlesTable.fetchArticles(articleIDs: articleIDs) } - public func fetchUnreadArticles(_ feedIDs: Set) -> Set
{ - return articlesTable.fetchUnreadArticles(feedIDs) + public func fetchUnreadArticles(_ webFeedID: Set) -> Set
{ + return articlesTable.fetchUnreadArticles(webFeedID) } - public func fetchTodayArticles(_ feedIDs: Set) -> Set
{ - return articlesTable.fetchArticlesSince(feedIDs, todayCutoffDate()) + public func fetchTodayArticles(_ webFeedIDs: Set) -> Set
{ + return articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate()) } - public func fetchStarredArticles(_ feedIDs: Set) -> Set
{ - return articlesTable.fetchStarredArticles(feedIDs) + public func fetchStarredArticles(_ webFeedIDs: Set) -> Set
{ + return articlesTable.fetchStarredArticles(webFeedIDs) } - public func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set) -> Set
{ - return articlesTable.fetchArticlesMatching(searchString, feedIDs) + public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set) -> Set
{ + return articlesTable.fetchArticlesMatching(searchString, webFeedIDs) } public func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set) -> Set
{ @@ -74,28 +74,28 @@ public final class ArticlesDatabase { // MARK: - Fetching Articles Async - public func fetchArticlesAsync(_ feedID: String, _ callback: @escaping ArticleSetBlock) { - articlesTable.fetchArticlesAsync(feedID, callback) + public func fetchArticlesAsync(_ webFeedID: String, _ callback: @escaping ArticleSetBlock) { + articlesTable.fetchArticlesAsync(webFeedID, callback) } public func fetchArticlesAsync(articleIDs: Set, _ callback: @escaping ArticleSetBlock) { articlesTable.fetchArticlesAsync(articleIDs: articleIDs, callback) } - public func fetchUnreadArticlesAsync(_ feedIDs: Set, _ callback: @escaping ArticleSetBlock) { - articlesTable.fetchUnreadArticlesAsync(feedIDs, callback) + public func fetchUnreadArticlesAsync(_ webFeedIDs: Set, _ callback: @escaping ArticleSetBlock) { + articlesTable.fetchUnreadArticlesAsync(webFeedIDs, callback) } - public func fetchTodayArticlesAsync(_ feedIDs: Set, _ callback: @escaping ArticleSetBlock) { - articlesTable.fetchArticlesSinceAsync(feedIDs, todayCutoffDate(), callback) + public func fetchTodayArticlesAsync(_ webFeedIDs: Set, _ callback: @escaping ArticleSetBlock) { + articlesTable.fetchArticlesSinceAsync(webFeedIDs, todayCutoffDate(), callback) } - public func fetchedStarredArticlesAsync(_ feedIDs: Set, _ callback: @escaping ArticleSetBlock) { - articlesTable.fetchStarredArticlesAsync(feedIDs, callback) + public func fetchedStarredArticlesAsync(_ webFeedIDs: Set, _ callback: @escaping ArticleSetBlock) { + articlesTable.fetchStarredArticlesAsync(webFeedIDs, callback) } - public func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set, _ callback: @escaping ArticleSetBlock) { - articlesTable.fetchArticlesMatchingAsync(searchString, feedIDs, callback) + public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set, _ callback: @escaping ArticleSetBlock) { + articlesTable.fetchArticlesMatchingAsync(searchString, webFeedIDs, callback) } public func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set, _ callback: @escaping ArticleSetBlock) { @@ -104,20 +104,20 @@ public final class ArticlesDatabase { // MARK: - Unread Counts - public func fetchUnreadCounts(for feedIDs: Set, _ callback: @escaping UnreadCountCompletionBlock) { - articlesTable.fetchUnreadCounts(feedIDs, callback) + public func fetchUnreadCounts(for webFeedIDs: Set, _ callback: @escaping UnreadCountCompletionBlock) { + articlesTable.fetchUnreadCounts(webFeedIDs, callback) } - public func fetchUnreadCountForToday(for feedIDs: Set, callback: @escaping (Int) -> Void) { - fetchUnreadCount(for: feedIDs, since: todayCutoffDate(), callback: callback) + public func fetchUnreadCountForToday(for webFeedIDs: Set, callback: @escaping (Int) -> Void) { + fetchUnreadCount(for: webFeedIDs, since: todayCutoffDate(), callback: callback) } - public func fetchUnreadCount(for feedIDs: Set, since: Date, callback: @escaping (Int) -> Void) { - articlesTable.fetchUnreadCount(feedIDs, since, callback) + public func fetchUnreadCount(for webFeedIDs: Set, since: Date, callback: @escaping (Int) -> Void) { + articlesTable.fetchUnreadCount(webFeedIDs, since, callback) } - public func fetchStarredAndUnreadCount(for feedIDs: Set, callback: @escaping (Int) -> Void) { - articlesTable.fetchStarredAndUnreadCount(feedIDs, callback) + public func fetchStarredAndUnreadCount(for webFeedIDs: Set, callback: @escaping (Int) -> Void) { + articlesTable.fetchStarredAndUnreadCount(webFeedIDs, callback) } public func fetchAllNonZeroUnreadCounts(_ callback: @escaping UnreadCountCompletionBlock) { @@ -126,9 +126,9 @@ public final class ArticlesDatabase { // MARK: - Saving and Updating Articles - /// Update articles and save new ones. The key for feedIDsAndItems is feedID. - public func update(feedIDsAndItems: [String: Set], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) { - articlesTable.update(feedIDsAndItems, defaultRead, completion) + /// Update articles and save new ones. The key for ewbFeedIDsAndItems is webFeedID. + public func update(webFeedIDsAndItems: [String: Set], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) { + articlesTable.update(webFeedIDsAndItems, defaultRead, completion) } public func ensureStatuses(_ articleIDs: Set, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool, completionHandler: (() -> ())? = nil) { @@ -166,8 +166,8 @@ public final class ArticlesDatabase { // These are to be used only at startup. These are to prevent the database from growing forever. /// Calls the various clean-up functions. - public func cleanupDatabaseAtStartup(subscribedToFeedIDs: Set) { - articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToFeedIDs) + public func cleanupDatabaseAtStartup(subscribedToWebFeedIDs: Set) { + articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToWebFeedIDs) } } diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index e0b924a05..02629bff6 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -48,16 +48,16 @@ final class ArticlesTable: DatabaseTable { // MARK: - Fetching Articles for Feed - func fetchArticles(_ feedID: String) -> Set
{ - return fetchArticles{ self.fetchArticlesForFeedID(feedID, withLimits: true, $0) } + func fetchArticles(_ webFeedID: String) -> Set
{ + return fetchArticles{ self.fetchArticlesForFeedID(webFeedID, withLimits: true, $0) } } - func fetchArticlesAsync(_ feedID: String, _ callback: @escaping ArticleSetBlock) { - fetchArticlesAsync({ self.fetchArticlesForFeedID(feedID, withLimits: true, $0) }, callback) + func fetchArticlesAsync(_ webFeedID: String, _ callback: @escaping ArticleSetBlock) { + fetchArticlesAsync({ self.fetchArticlesForFeedID(webFeedID, withLimits: true, $0) }, callback) } - private func fetchArticlesForFeedID(_ feedID: String, withLimits: Bool, _ database: FMDatabase) -> Set
{ - return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject], withLimits: withLimits) + private func fetchArticlesForFeedID(_ webFeedID: String, withLimits: Bool, _ database: FMDatabase) -> Set
{ + return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [webFeedID as AnyObject], withLimits: withLimits) } // MARK: - Fetching Articles by articleID @@ -82,65 +82,65 @@ final class ArticlesTable: DatabaseTable { // MARK: - Fetching Unread Articles - func fetchUnreadArticles(_ feedIDs: Set) -> Set
{ - return fetchArticles{ self.fetchUnreadArticles(feedIDs, $0) } + func fetchUnreadArticles(_ webFeedIDs: Set) -> Set
{ + return fetchArticles{ self.fetchUnreadArticles(webFeedIDs, $0) } } - func fetchUnreadArticlesAsync(_ feedIDs: Set, _ callback: @escaping ArticleSetBlock) { - fetchArticlesAsync({ self.fetchUnreadArticles(feedIDs, $0) }, callback) + func fetchUnreadArticlesAsync(_ webFeedIDs: Set, _ callback: @escaping ArticleSetBlock) { + fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, $0) }, callback) } - private func fetchUnreadArticles(_ feedIDs: Set, _ database: FMDatabase) -> Set
{ + private func fetchUnreadArticles(_ webFeedIDs: Set, _ database: FMDatabase) -> Set
{ // select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0 - if feedIDs.isEmpty { + if webFeedIDs.isEmpty { return Set
() } - let parameters = feedIDs.map { $0 as AnyObject } - let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))! + let parameters = webFeedIDs.map { $0 as AnyObject } + let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! let whereClause = "feedID in \(placeholders) and read=0" return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: true) } // MARK: - Fetching Today Articles - func fetchArticlesSince(_ feedIDs: Set, _ cutoffDate: Date) -> Set
{ - return fetchArticles{ self.fetchArticlesSince(feedIDs, cutoffDate, $0) } + func fetchArticlesSince(_ webFeedIDs: Set, _ cutoffDate: Date) -> Set
{ + return fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) } } - func fetchArticlesSinceAsync(_ feedIDs: Set, _ cutoffDate: Date, _ callback: @escaping ArticleSetBlock) { - fetchArticlesAsync({ self.fetchArticlesSince(feedIDs, cutoffDate, $0) }, callback) + func fetchArticlesSinceAsync(_ webFeedIDs: Set, _ cutoffDate: Date, _ callback: @escaping ArticleSetBlock) { + fetchArticlesAsync({ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }, callback) } - private func fetchArticlesSince(_ feedIDs: Set, _ cutoffDate: Date, _ database: FMDatabase) -> Set
{ + private func fetchArticlesSince(_ webFeedIDs: Set, _ cutoffDate: Date, _ database: FMDatabase) -> Set
{ // select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and (datePublished > ? || (datePublished is null and dateArrived > ?) // // datePublished may be nil, so we fall back to dateArrived. - if feedIDs.isEmpty { + if webFeedIDs.isEmpty { return Set
() } - let parameters = feedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject] - let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))! + let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject] + let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! let whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and userDeleted = 0" return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false) } // MARK: - Fetching Starred Articles - func fetchStarredArticles(_ feedIDs: Set) -> Set
{ - return fetchArticles{ self.fetchStarredArticles(feedIDs, $0) } + func fetchStarredArticles(_ webFeedIDs: Set) -> Set
{ + return fetchArticles{ self.fetchStarredArticles(webFeedIDs, $0) } } - func fetchStarredArticlesAsync(_ feedIDs: Set, _ callback: @escaping ArticleSetBlock) { - fetchArticlesAsync({ self.fetchStarredArticles(feedIDs, $0) }, callback) + func fetchStarredArticlesAsync(_ webFeedIDs: Set, _ callback: @escaping ArticleSetBlock) { + fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, $0) }, callback) } - private func fetchStarredArticles(_ feedIDs: Set, _ database: FMDatabase) -> Set
{ + private func fetchStarredArticles(_ webFeedIDs: Set, _ database: FMDatabase) -> Set
{ // select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred = 1 and userDeleted = 0; - if feedIDs.isEmpty { + if webFeedIDs.isEmpty { return Set
() } - let parameters = feedIDs.map { $0 as AnyObject } - let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))! + let parameters = webFeedIDs.map { $0 as AnyObject } + let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! let whereClause = "feedID in \(placeholders) and starred = 1 and userDeleted = 0" return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false) } @@ -155,9 +155,9 @@ final class ArticlesTable: DatabaseTable { return articles } - func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set) -> Set
{ + func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set) -> Set
{ var articles = fetchArticlesMatching(searchString) - articles = articles.filter{ feedIDs.contains($0.feedID) } + articles = articles.filter{ webFeedIDs.contains($0.webFeedID) } return articles } @@ -167,18 +167,18 @@ final class ArticlesTable: DatabaseTable { return articles } - func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set, _ callback: @escaping ArticleSetBlock) { - fetchArticlesAsync({ self.fetchArticlesMatching(searchString, feedIDs, $0) }, callback) + func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set, _ callback: @escaping ArticleSetBlock) { + fetchArticlesAsync({ self.fetchArticlesMatching(searchString, webFeedIDs, $0) }, callback) } func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set, _ callback: @escaping ArticleSetBlock) { fetchArticlesAsync({ self.fetchArticlesMatchingWithArticleIDs(searchString, articleIDs, $0) }, callback) } - private func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set, _ database: FMDatabase) -> Set
{ + private func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set, _ database: FMDatabase) -> Set
{ let articles = fetchArticlesMatching(searchString, database) // TODO: include the feedIDs in the SQL rather than filtering here. - return articles.filter{ feedIDs.contains($0.feedID) } + return articles.filter{ webFeedIDs.contains($0.webFeedID) } } private func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set, _ database: FMDatabase) -> Set
{ @@ -216,8 +216,8 @@ final class ArticlesTable: DatabaseTable { // MARK: - Updating - func update(_ feedIDsAndItems: [String: Set], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) { - if feedIDsAndItems.isEmpty { + func update(_ webFeedIDsAndItems: [String: Set], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) { + if webFeedIDsAndItems.isEmpty { completion(nil, nil) return } @@ -232,7 +232,7 @@ final class ArticlesTable: DatabaseTable { // 8. Update search index. var articleIDs = Set() - for (_, parsedItems) in feedIDsAndItems { + for (_, parsedItems) in webFeedIDsAndItems { articleIDs.formUnion(parsedItems.articleIDs()) } @@ -240,7 +240,7 @@ final class ArticlesTable: DatabaseTable { let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1 assert(statusesDictionary.count == articleIDs.count) - let allIncomingArticles = Article.articlesWithFeedIDsAndItems(feedIDsAndItems, self.accountID, statusesDictionary) //2 + let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedIDsAndItems, self.accountID, statusesDictionary) //2 if allIncomingArticles.isEmpty { self.callUpdateArticlesCompletionBlock(nil, nil, completion) return @@ -292,8 +292,8 @@ final class ArticlesTable: DatabaseTable { // MARK: - Unread Counts - func fetchUnreadCounts(_ feedIDs: Set, _ completion: @escaping UnreadCountCompletionBlock) { - if feedIDs.isEmpty { + func fetchUnreadCounts(_ webFeedIDs: Set, _ completion: @escaping UnreadCountCompletionBlock) { + if webFeedIDs.isEmpty { completion(UnreadCountDictionary()) return } @@ -301,8 +301,8 @@ final class ArticlesTable: DatabaseTable { var unreadCountDictionary = UnreadCountDictionary() queue.fetch { (database) in - for feedID in feedIDs { - unreadCountDictionary[feedID] = self.fetchUnreadCount(feedID, database) + for webFeedID in webFeedIDs { + unreadCountDictionary[webFeedID] = self.fetchUnreadCount(webFeedID, database) } DispatchQueue.main.async { @@ -311,20 +311,20 @@ final class ArticlesTable: DatabaseTable { } } - func fetchUnreadCount(_ feedIDs: Set, _ since: Date, _ callback: @escaping (Int) -> Void) { + func fetchUnreadCount(_ webFeedIDs: Set, _ since: Date, _ callback: @escaping (Int) -> Void) { // Get unread count for today, for instance. - if feedIDs.isEmpty { + if webFeedIDs.isEmpty { callback(0) return } queue.fetch { (database) in - let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))! + let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and read=0 and userDeleted=0;" var parameters = [Any]() - parameters += Array(feedIDs) as [Any] + parameters += Array(webFeedIDs) as [Any] parameters += [since] as [Any] parameters += [since] as [Any] @@ -354,8 +354,8 @@ final class ArticlesTable: DatabaseTable { var d = UnreadCountDictionary() while resultSet.next() { let unreadCount = resultSet.long(forColumnIndex: 1) - if let feedID = resultSet.string(forColumnIndex: 0) { - d[feedID] = unreadCount + if let webFeedID = resultSet.string(forColumnIndex: 0) { + d[webFeedID] = unreadCount } } @@ -365,16 +365,16 @@ final class ArticlesTable: DatabaseTable { } } - func fetchStarredAndUnreadCount(_ feedIDs: Set, _ callback: @escaping (Int) -> Void) { - if feedIDs.isEmpty { + func fetchStarredAndUnreadCount(_ webFeedIDs: Set, _ callback: @escaping (Int) -> Void) { + if webFeedIDs.isEmpty { callback(0) return } queue.fetch { (database) in - let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))! + let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and starred=1 and userDeleted=0;" - let parameters = Array(feedIDs) as [Any] + let parameters = Array(webFeedIDs) as [Any] let unreadCount = self.numberWithSQLAndParameters(sql, parameters, in: database) @@ -439,14 +439,14 @@ final class ArticlesTable: DatabaseTable { /// Delete articles from feeds that are no longer in the current set of subscribed-to feeds. /// This deletes from the articles and articleStatuses tables, /// and, via a trigger, it also deletes from the search index. - func deleteArticlesNotInSubscribedToFeedIDs(_ feedIDs: Set) { - if feedIDs.isEmpty { + func deleteArticlesNotInSubscribedToFeedIDs(_ webFeedIDs: Set) { + if webFeedIDs.isEmpty { return } queue.run { (database) in - let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))! + let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! let sql = "select articleID from articles where feedID not in \(placeholders);" - let parameters = Array(feedIDs) as [Any] + let parameters = Array(webFeedIDs) as [Any] guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else { return } @@ -540,7 +540,7 @@ private extension ArticlesTable { assertionFailure("Expected status.") return nil } - guard let feedID = row.string(forColumn: DatabaseKey.feedID) else { + guard let webFeedID = row.string(forColumn: DatabaseKey.feedID) else { assertionFailure("Expected feedID.") return nil } @@ -560,7 +560,7 @@ private extension ArticlesTable { let datePublished = row.date(forColumn: DatabaseKey.datePublished) let dateModified = row.date(forColumn: DatabaseKey.dateModified) - let databaseArticle = DatabaseArticle(articleID: articleID, feedID: feedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, status: status) + let databaseArticle = DatabaseArticle(articleID: articleID, webFeedID: webFeedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, status: status) databaseArticlesCache[articleID] = databaseArticle return databaseArticle } @@ -583,14 +583,14 @@ private extension ArticlesTable { } } - func fetchUnreadCount(_ feedID: String, _ database: FMDatabase) -> Int { + func fetchUnreadCount(_ webFeedID: String, _ database: FMDatabase) -> Int { // Count only the articles that would appear in the UI. // * Must be unread. // * Must not be deleted. // * Must be either 1) starred or 2) dateArrived must be newer than cutoff date. let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0 and (starred=1 or dateArrived>?);" - return numberWithSQLAndParameters(sql, [feedID, articleCutoffDate], in: database) + return numberWithSQLAndParameters(sql, [webFeedID, articleCutoffDate], in: database) } func fetchArticlesMatching(_ searchString: String, _ database: FMDatabase) -> Set
{ diff --git a/Frameworks/ArticlesDatabase/DatabaseArticle.swift b/Frameworks/ArticlesDatabase/DatabaseArticle.swift index 7ac09c90c..d029f7113 100644 --- a/Frameworks/ArticlesDatabase/DatabaseArticle.swift +++ b/Frameworks/ArticlesDatabase/DatabaseArticle.swift @@ -15,7 +15,7 @@ import Articles struct DatabaseArticle: Hashable { let articleID: String - let feedID: String + let webFeedID: String let uniqueID: String let title: String? let contentHTML: String? diff --git a/Frameworks/ArticlesDatabase/Extensions/Article+Database.swift b/Frameworks/ArticlesDatabase/Extensions/Article+Database.swift index 2fd0d48c8..608e863bd 100644 --- a/Frameworks/ArticlesDatabase/Extensions/Article+Database.swift +++ b/Frameworks/ArticlesDatabase/Extensions/Article+Database.swift @@ -14,10 +14,10 @@ import RSParser extension Article { init(databaseArticle: DatabaseArticle, accountID: String, authors: Set?, attachments: Set?) { - self.init(accountID: accountID, articleID: databaseArticle.articleID, feedID: databaseArticle.feedID, uniqueID: databaseArticle.uniqueID, title: databaseArticle.title, contentHTML: databaseArticle.contentHTML, contentText: databaseArticle.contentText, url: databaseArticle.url, externalURL: databaseArticle.externalURL, summary: databaseArticle.summary, imageURL: databaseArticle.imageURL, bannerImageURL: databaseArticle.bannerImageURL, datePublished: databaseArticle.datePublished, dateModified: databaseArticle.dateModified, authors: authors, attachments: attachments, status: databaseArticle.status) + self.init(accountID: accountID, articleID: databaseArticle.articleID, webFeedID: databaseArticle.webFeedID, uniqueID: databaseArticle.uniqueID, title: databaseArticle.title, contentHTML: databaseArticle.contentHTML, contentText: databaseArticle.contentText, url: databaseArticle.url, externalURL: databaseArticle.externalURL, summary: databaseArticle.summary, imageURL: databaseArticle.imageURL, bannerImageURL: databaseArticle.bannerImageURL, datePublished: databaseArticle.datePublished, dateModified: databaseArticle.dateModified, authors: authors, attachments: attachments, status: databaseArticle.status) } - init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, feedID: String, status: ArticleStatus) { + init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, webFeedID: String, status: ArticleStatus) { let authors = Author.authorsWithParsedAuthors(parsedItem.authors) let attachments = Attachment.attachmentsWithParsedAttachments(parsedItem.attachments) @@ -35,7 +35,7 @@ extension Article { dateModified = nil } - self.init(accountID: accountID, articleID: parsedItem.syncServiceID, feedID: feedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, attachments: attachments, status: status) + self.init(accountID: accountID, articleID: parsedItem.syncServiceID, webFeedID: webFeedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, attachments: attachments, status: status) } private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath, _ otherArticle: Article, _ key: String, _ dictionary: inout DatabaseDictionary) { @@ -84,11 +84,11 @@ extension Article { // return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) }) // } - static func articlesWithFeedIDsAndItems(_ feedIDsAndItems: [String: Set], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set
{ + static func articlesWithWebFeedIDsAndItems(_ webFeedIDsAndItems: [String: Set], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set
{ let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now var articles = Set
() - for (feedID, parsedItems) in feedIDsAndItems { - let feedArticles = Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) }) + for (webFeedID, parsedItems) in webFeedIDsAndItems { + let feedArticles = Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: statusesDictionary[$0.articleID]!) }) articles.formUnion(feedArticles) } return articles @@ -101,7 +101,7 @@ extension Article: DatabaseObject { var d = DatabaseDictionary() d[DatabaseKey.articleID] = articleID - d[DatabaseKey.feedID] = feedID + d[DatabaseKey.feedID] = webFeedID d[DatabaseKey.uniqueID] = uniqueID if let title = title { diff --git a/Frameworks/ArticlesDatabase/Extensions/ParsedArticle+Database.swift b/Frameworks/ArticlesDatabase/Extensions/ParsedArticle+Database.swift index 271876057..e59539d11 100644 --- a/Frameworks/ArticlesDatabase/Extensions/ParsedArticle+Database.swift +++ b/Frameworks/ArticlesDatabase/Extensions/ParsedArticle+Database.swift @@ -17,6 +17,6 @@ extension ParsedItem { return s } // Must be same calculation as for Article. - return Article.calculatedArticleID(feedID: feedURL, uniqueID: uniqueID) + return Article.calculatedArticleID(webFeedID: feedURL, uniqueID: uniqueID) } } diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 999d91653..b9eba6e9d 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -33,7 +33,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, var faviconDownloader: FaviconDownloader! var imageDownloader: ImageDownloader! var authorAvatarDownloader: AuthorAvatarDownloader! - var feedIconDownloader: FeedIconDownloader! + var webFeedIconDownloader: WebFeedIconDownloader! var appName: String! var refreshTimer: AccountRefreshTimer? @@ -186,7 +186,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, imageDownloader = ImageDownloader(folder: imagesFolder) authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader) - feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder) + webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder) updateSortMenuItems() updateGroupByFeedMenuItem() @@ -195,7 +195,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, mainWindowController?.window?.center() } - NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) DispatchQueue.main.async { @@ -299,12 +299,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } } - @objc func feedSettingDidChange(_ note: Notification) { + @objc func webFeedSettingDidChange(_ note: Notification) { - guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else { + guard let feed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else { return } - if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL { + if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL { let _ = faviconDownloader.favicon(for: feed) } } diff --git a/Mac/Inspector/Inspector.storyboard b/Mac/Inspector/Inspector.storyboard index b35d220cf..6a01d2039 100644 --- a/Mac/Inspector/Inspector.storyboard +++ b/Mac/Inspector/Inspector.storyboard @@ -31,7 +31,7 @@ - + diff --git a/Mac/Inspector/FeedInspectorViewController.swift b/Mac/Inspector/WebFeedInspectorViewController.swift similarity index 88% rename from Mac/Inspector/FeedInspectorViewController.swift rename to Mac/Inspector/WebFeedInspectorViewController.swift index 964668798..9db23b335 100644 --- a/Mac/Inspector/FeedInspectorViewController.swift +++ b/Mac/Inspector/WebFeedInspectorViewController.swift @@ -10,7 +10,7 @@ import AppKit import Articles import Account -final class FeedInspectorViewController: NSViewController, Inspector { +final class WebFeedInspectorViewController: NSViewController, Inspector { @IBOutlet weak var iconView: IconView! @IBOutlet weak var nameTextField: NSTextField? @@ -19,7 +19,7 @@ final class FeedInspectorViewController: NSViewController, Inspector { @IBOutlet weak var isNotifyAboutNewArticlesCheckBox: NSButton! @IBOutlet weak var isReaderViewAlwaysOnCheckBox: NSButton? - private var feed: Feed? { + private var feed: WebFeed? { didSet { if feed != oldValue { updateUI() @@ -37,7 +37,7 @@ final class FeedInspectorViewController: NSViewController, Inspector { } func canInspect(_ objects: [Any]) -> Bool { - return objects.count == 1 && objects.first is Feed + return objects.count == 1 && objects.first is WebFeed } // MARK: NSViewController @@ -64,7 +64,7 @@ final class FeedInspectorViewController: NSViewController, Inspector { } -extension FeedInspectorViewController: NSTextFieldDelegate { +extension WebFeedInspectorViewController: NSTextFieldDelegate { func controlTextDidChange(_ note: Notification) { guard let feed = feed, let nameTextField = nameTextField else { @@ -75,10 +75,10 @@ extension FeedInspectorViewController: NSTextFieldDelegate { } -private extension FeedInspectorViewController { +private extension WebFeedInspectorViewController { func updateFeed() { - guard let objects = objects, objects.count == 1, let singleFeed = objects.first as? Feed else { + guard let objects = objects, objects.count == 1, let singleFeed = objects.first as? WebFeed else { feed = nil return } @@ -101,7 +101,7 @@ private extension FeedInspectorViewController { return } - if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) { + if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: feed) { iconView.iconImage = feedIcon return } diff --git a/Mac/MainWindow/AddFeed/AddFeedController.swift b/Mac/MainWindow/AddFeed/AddFeedController.swift index 3760dbe88..13ac321fa 100644 --- a/Mac/MainWindow/AddFeed/AddFeedController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedController.swift @@ -53,14 +53,14 @@ class AddFeedController: AddFeedWindowControllerDelegate { } let account = accountAndFolderSpecifier.account - if account.hasFeed(withURL: url.absoluteString) { + if account.hasWebFeed(withURL: url.absoluteString) { showAlreadySubscribedError(url.absoluteString) return } BatchUpdate.shared.start() - account.createFeed(url: url.absoluteString, name: title, container: container) { result in + account.createWebFeed(url: url.absoluteString, name: title, container: container) { result in DispatchQueue.main.async { self.endShowingProgress() @@ -70,7 +70,7 @@ class AddFeedController: AddFeedWindowControllerDelegate { switch result { case .success(let feed): - NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) + NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed]) case .failure(let error): switch error { case AccountError.createErrorAlreadySubscribed: diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index f7792d310..7bf7ea6b5 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -172,7 +172,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } } - if let feed = currentFeedOrFolder as? Feed, let noteObject = noteObject as? Feed { + if let feed = currentFeedOrFolder as? WebFeed, let noteObject = noteObject as? WebFeed { if feed == noteObject { updateWindowTitle() return @@ -482,7 +482,7 @@ extension MainWindowController: TimelineContainerViewControllerDelegate { if let articles = articles { if articles.count == 1 { activityManager.reading(fetcher: nil, article: articles.first) - if articles.first?.feed?.isArticleExtractorAlwaysOn ?? false { + if articles.first?.webFeed?.isArticleExtractorAlwaysOn ?? false { detailState = .loading startArticleExtractorForCurrentLink() } else { diff --git a/Mac/MainWindow/Sidebar/PasteboardFolder.swift b/Mac/MainWindow/Sidebar/PasteboardFolder.swift index 2a7e14eb8..60c42154c 100644 --- a/Mac/MainWindow/Sidebar/PasteboardFolder.swift +++ b/Mac/MainWindow/Sidebar/PasteboardFolder.swift @@ -52,7 +52,7 @@ struct PasteboardFolder: Hashable { } if let foundType = pasteboardType { - if let folderDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardFeedDictionary { + if let folderDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardWebFeedDictionary { self.init(dictionary: folderDictionary) return } @@ -72,7 +72,7 @@ struct PasteboardFolder: Hashable { // MARK: - Writing func internalDictionary() -> PasteboardFolderDictionary { - var d = PasteboardFeedDictionary() + var d = PasteboardWebFeedDictionary() d[PasteboardFolder.Key.name] = name if let folderID = folderID { d[PasteboardFolder.Key.folderID] = folderID @@ -131,7 +131,7 @@ private extension FolderPasteboardWriter { return PasteboardFolder(name: folder.name ?? "", folderID: String(folder.folderID), accountID: folder.account?.accountID) } - var internalDictionary: PasteboardFeedDictionary { + var internalDictionary: PasteboardWebFeedDictionary { return pasteboardFolder.internalDictionary() } } diff --git a/Mac/MainWindow/Sidebar/PasteboardFeed.swift b/Mac/MainWindow/Sidebar/PasteboardWebFeed.swift similarity index 50% rename from Mac/MainWindow/Sidebar/PasteboardFeed.swift rename to Mac/MainWindow/Sidebar/PasteboardWebFeed.swift index 5e0bc89e3..1c84cb78b 100644 --- a/Mac/MainWindow/Sidebar/PasteboardFeed.swift +++ b/Mac/MainWindow/Sidebar/PasteboardWebFeed.swift @@ -11,9 +11,9 @@ import Articles import Account import RSCore -typealias PasteboardFeedDictionary = [String: String] +typealias PasteboardWebFeedDictionary = [String: String] -struct PasteboardFeed: Hashable { +struct PasteboardWebFeed: Hashable { private struct Key { static let url = "URL" @@ -23,12 +23,12 @@ struct PasteboardFeed: Hashable { // Internal static let accountID = "accountID" static let accountType = "accountType" - static let feedID = "feedID" + static let webFeedID = "webFeedID" static let editedName = "editedName" } let url: String - let feedID: String? + let webFeedID: String? let homePageURL: String? let name: String? let editedName: String? @@ -36,9 +36,9 @@ struct PasteboardFeed: Hashable { let accountType: AccountType? let isLocalFeed: Bool - init(url: String, feedID: String?, homePageURL: String?, name: String?, editedName: String?, accountID: String?, accountType: AccountType?) { + init(url: String, webFeedID: String?, homePageURL: String?, name: String?, editedName: String?, accountID: String?, accountType: AccountType?) { self.url = url.rs_normalizedURL() - self.feedID = feedID + self.webFeedID = webFeedID self.homePageURL = homePageURL?.rs_normalizedURL() self.name = name self.editedName = editedName @@ -49,7 +49,7 @@ struct PasteboardFeed: Hashable { // MARK: - Reading - init?(dictionary: PasteboardFeedDictionary) { + init?(dictionary: PasteboardWebFeedDictionary) { guard let url = dictionary[Key.url] else { return nil } @@ -57,7 +57,7 @@ struct PasteboardFeed: Hashable { let homePageURL = dictionary[Key.homePageURL] let name = dictionary[Key.name] let accountID = dictionary[Key.accountID] - let feedID = dictionary[Key.feedID] + let webFeedID = dictionary[Key.webFeedID] let editedName = dictionary[Key.editedName] var accountType: AccountType? = nil @@ -65,19 +65,19 @@ struct PasteboardFeed: Hashable { accountType = AccountType(rawValue: accountTypeInt) } - self.init(url: url, feedID: feedID, homePageURL: homePageURL, name: name, editedName: editedName, accountID: accountID, accountType: accountType) + self.init(url: url, webFeedID: webFeedID, homePageURL: homePageURL, name: name, editedName: editedName, accountID: accountID, accountType: accountType) } init?(pasteboardItem: NSPasteboardItem) { var pasteboardType: NSPasteboard.PasteboardType? - if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIInternalType) { - pasteboardType = FeedPasteboardWriter.feedUTIInternalType + if pasteboardItem.types.contains(WebFeedPasteboardWriter.webFeedUTIInternalType) { + pasteboardType = WebFeedPasteboardWriter.webFeedUTIInternalType } - else if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIType) { - pasteboardType = FeedPasteboardWriter.feedUTIType + else if pasteboardItem.types.contains(WebFeedPasteboardWriter.webFeedUTIType) { + pasteboardType = WebFeedPasteboardWriter.webFeedUTIType } if let foundType = pasteboardType { - if let feedDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardFeedDictionary { + if let feedDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardWebFeedDictionary { self.init(dictionary: feedDictionary) return } @@ -94,7 +94,7 @@ struct PasteboardFeed: Hashable { if let foundType = pasteboardType { if let possibleURLString = pasteboardItem.string(forType: foundType) { if possibleURLString.rs_stringMayBeURL() { - self.init(url: possibleURLString, feedID: nil, homePageURL: nil, name: nil, editedName: nil, accountID: nil, accountType: nil) + self.init(url: possibleURLString, webFeedID: nil, homePageURL: nil, name: nil, editedName: nil, accountID: nil, accountType: nil) return } } @@ -103,18 +103,18 @@ struct PasteboardFeed: Hashable { return nil } - static func pasteboardFeeds(with pasteboard: NSPasteboard) -> Set? { + static func pasteboardFeeds(with pasteboard: NSPasteboard) -> Set? { guard let items = pasteboard.pasteboardItems else { return nil } - let feeds = items.compactMap { PasteboardFeed(pasteboardItem: $0) } - return feeds.isEmpty ? nil : Set(feeds) + let webFeeds = items.compactMap { PasteboardWebFeed(pasteboardItem: $0) } + return webFeeds.isEmpty ? nil : Set(webFeeds) } // MARK: - Writing - func exportDictionary() -> PasteboardFeedDictionary { - var d = PasteboardFeedDictionary() + func exportDictionary() -> PasteboardWebFeedDictionary { + var d = PasteboardWebFeedDictionary() d[Key.url] = url d[Key.homePageURL] = homePageURL ?? "" if let nameForDisplay = editedName ?? name { @@ -123,54 +123,54 @@ struct PasteboardFeed: Hashable { return d } - func internalDictionary() -> PasteboardFeedDictionary { - var d = PasteboardFeedDictionary() - d[PasteboardFeed.Key.feedID] = feedID - d[PasteboardFeed.Key.url] = url + func internalDictionary() -> PasteboardWebFeedDictionary { + var d = PasteboardWebFeedDictionary() + d[PasteboardWebFeed.Key.webFeedID] = webFeedID + d[PasteboardWebFeed.Key.url] = url if let homePageURL = homePageURL { - d[PasteboardFeed.Key.homePageURL] = homePageURL + d[PasteboardWebFeed.Key.homePageURL] = homePageURL } if let name = name { - d[PasteboardFeed.Key.name] = name + d[PasteboardWebFeed.Key.name] = name } if let editedName = editedName { - d[PasteboardFeed.Key.editedName] = editedName + d[PasteboardWebFeed.Key.editedName] = editedName } if let accountID = accountID { - d[PasteboardFeed.Key.accountID] = accountID + d[PasteboardWebFeed.Key.accountID] = accountID } if let accountType = accountType { - d[PasteboardFeed.Key.accountType] = String(accountType.rawValue) + d[PasteboardWebFeed.Key.accountType] = String(accountType.rawValue) } return d } } -extension Feed: PasteboardWriterOwner { +extension WebFeed: PasteboardWriterOwner { public var pasteboardWriter: NSPasteboardWriting { - return FeedPasteboardWriter(feed: self) + return WebFeedPasteboardWriter(webFeed: self) } } -@objc final class FeedPasteboardWriter: NSObject, NSPasteboardWriting { +@objc final class WebFeedPasteboardWriter: NSObject, NSPasteboardWriting { - private let feed: Feed - static let feedUTI = "com.ranchero.feed" - static let feedUTIType = NSPasteboard.PasteboardType(rawValue: feedUTI) - static let feedUTIInternal = "com.ranchero.NetNewsWire-Evergreen.internal.feed" - static let feedUTIInternalType = NSPasteboard.PasteboardType(rawValue: feedUTIInternal) + private let webFeed: WebFeed + static let webFeedUTI = "com.ranchero.webFeed" + static let webFeedUTIType = NSPasteboard.PasteboardType(rawValue: webFeedUTI) + static let webFeedUTIInternal = "com.ranchero.NetNewsWire-Evergreen.internal.webFeed" + static let webFeedUTIInternalType = NSPasteboard.PasteboardType(rawValue: webFeedUTIInternal) - init(feed: Feed) { - self.feed = feed + init(webFeed: WebFeed) { + self.webFeed = webFeed } // MARK: - NSPasteboardWriting func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] { - return [FeedPasteboardWriter.feedUTIType, .URL, .string, FeedPasteboardWriter.feedUTIInternalType] + return [WebFeedPasteboardWriter.webFeedUTIType, .URL, .string, WebFeedPasteboardWriter.webFeedUTIInternalType] } func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? { @@ -179,12 +179,12 @@ extension Feed: PasteboardWriterOwner { switch type { case .string: - plist = feed.nameForDisplay + plist = webFeed.nameForDisplay case .URL: - plist = feed.url - case FeedPasteboardWriter.feedUTIType: + plist = webFeed.url + case WebFeedPasteboardWriter.webFeedUTIType: plist = exportDictionary - case FeedPasteboardWriter.feedUTIInternalType: + case WebFeedPasteboardWriter.webFeedUTIInternalType: plist = internalDictionary default: plist = nil @@ -194,17 +194,17 @@ extension Feed: PasteboardWriterOwner { } } -private extension FeedPasteboardWriter { +private extension WebFeedPasteboardWriter { - var pasteboardFeed: PasteboardFeed { - return PasteboardFeed(url: feed.url, feedID: feed.feedID, homePageURL: feed.homePageURL, name: feed.name, editedName: feed.editedName, accountID: feed.account?.accountID, accountType: feed.account?.type) + var pasteboardFeed: PasteboardWebFeed { + return PasteboardWebFeed(url: webFeed.url, webFeedID: webFeed.webFeedID, homePageURL: webFeed.homePageURL, name: webFeed.name, editedName: webFeed.editedName, accountID: webFeed.account?.accountID, accountType: webFeed.account?.type) } - var exportDictionary: PasteboardFeedDictionary { + var exportDictionary: PasteboardWebFeedDictionary { return pasteboardFeed.exportDictionary() } - var internalDictionary: PasteboardFeedDictionary { + var internalDictionary: PasteboardWebFeedDictionary { return pasteboardFeed.internalDictionary() } } diff --git a/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift b/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift index 5185d6f0c..09935f066 100644 --- a/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift +++ b/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift @@ -55,7 +55,7 @@ import Account func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation { let draggedFolders = PasteboardFolder.pasteboardFolders(with: info.draggingPasteboard) - let draggedFeeds = PasteboardFeed.pasteboardFeeds(with: info.draggingPasteboard) + let draggedFeeds = PasteboardWebFeed.pasteboardFeeds(with: info.draggingPasteboard) if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) { return SidebarOutlineDataSource.dragOperationNone } @@ -91,7 +91,7 @@ import Account func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool { let draggedFolders = PasteboardFolder.pasteboardFolders(with: info.draggingPasteboard) - let draggedFeeds = PasteboardFeed.pasteboardFeeds(with: info.draggingPasteboard) + let draggedFeeds = PasteboardWebFeed.pasteboardFeeds(with: info.draggingPasteboard) if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) { return false } @@ -136,7 +136,7 @@ private extension SidebarOutlineDataSource { // Don’t allow PseudoFeed to be dragged. // This will have to be revisited later. For instance, // user-created smart feeds should be draggable, maybe. - return node.representedObject is Folder || node.representedObject is Feed + return node.representedObject is Folder || node.representedObject is WebFeed } // MARK: - Drag and Drop @@ -145,7 +145,7 @@ private extension SidebarOutlineDataSource { case empty, singleLocal, singleNonLocal, multipleLocal, multipleNonLocal, mixed } - func draggedFeedContentsType(_ draggedFeeds: Set) -> DraggedFeedsContentsType { + func draggedFeedContentsType(_ draggedFeeds: Set) -> DraggedFeedsContentsType { if draggedFeeds.isEmpty { return .empty } @@ -173,14 +173,14 @@ private extension SidebarOutlineDataSource { return .multipleNonLocal } - func singleNonLocalFeed(from feeds: Set) -> PasteboardFeed? { + func singleNonLocalFeed(from feeds: Set) -> PasteboardWebFeed? { guard feeds.count == 1, let feed = feeds.first else { return nil } return feed.isLocalFeed ? nil : feed } - func validateSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation { + func validateSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation { // A non-local feed should always drag on to an Account or Folder node, with NSOutlineViewDropOnItemIndex — since we don’t know where it would sort till we read the feed. guard let dropTargetNode = ancestorThatCanAcceptNonLocalFeed(parentNode) else { return SidebarOutlineDataSource.dragOperationNone @@ -191,7 +191,7 @@ private extension SidebarOutlineDataSource { return .copy } - func validateSingleLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation { + func validateSingleLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation { // A local feed should always drag on to an Account or Folder node, and we can provide an index. guard let dropTargetNode = ancestorThatCanAcceptLocalFeed(parentNode) else { return SidebarOutlineDataSource.dragOperationNone @@ -212,7 +212,7 @@ private extension SidebarOutlineDataSource { return localDragOperation() } - func validateLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set, _ parentNode: Node, _ index: Int) -> NSDragOperation { + func validateLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set, _ parentNode: Node, _ index: Int) -> NSDragOperation { // Local feeds should always drag on to an Account or Folder node, and index should be NSOutlineViewDropOnItemIndex since we can’t provide multiple indexes. guard let dropTargetNode = ancestorThatCanAcceptLocalFeed(parentNode) else { return SidebarOutlineDataSource.dragOperationNone @@ -244,7 +244,7 @@ private extension SidebarOutlineDataSource { if let folder = node.representedObject as? Folder { return folder.account } - if let feed = node.representedObject as? Feed { + if let feed = node.representedObject as? WebFeed { return feed.account } return nil @@ -303,12 +303,12 @@ private extension SidebarOutlineDataSource { return localDragOperation() } - func copyFeedInAccount(node: Node, to parentNode: Node) { - guard let feed = node.representedObject as? Feed, let destination = parentNode.representedObject as? Container else { + func copyWebFeedInAccount(node: Node, to parentNode: Node) { + guard let feed = node.representedObject as? WebFeed, let destination = parentNode.representedObject as? Container else { return } - destination.account?.addFeed(feed, to: destination) { result in + destination.account?.addWebFeed(feed, to: destination) { result in switch result { case .success: break @@ -318,15 +318,15 @@ private extension SidebarOutlineDataSource { } } - func moveFeedInAccount(node: Node, to parentNode: Node) { - guard let feed = node.representedObject as? Feed, + func moveWebFeedInAccount(node: Node, to parentNode: Node) { + guard let feed = node.representedObject as? WebFeed, let source = node.parent?.representedObject as? Container, let destination = parentNode.representedObject as? Container else { return } BatchUpdate.shared.start() - source.account?.moveFeed(feed, from: source, to: destination) { result in + source.account?.moveWebFeed(feed, from: source, to: destination) { result in BatchUpdate.shared.end() switch result { case .success: @@ -337,15 +337,15 @@ private extension SidebarOutlineDataSource { } } - func copyFeedBetweenAccounts(node: Node, to parentNode: Node) { - guard let feed = node.representedObject as? Feed, + func copyWebFeedBetweenAccounts(node: Node, to parentNode: Node) { + guard let feed = node.representedObject as? WebFeed, let destinationAccount = nodeAccount(parentNode), let destinationContainer = parentNode.representedObject as? Container else { return } - if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) { - destinationAccount.addFeed(existingFeed, to: destinationContainer) { result in + if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) { + destinationAccount.addWebFeed(existingFeed, to: destinationContainer) { result in switch result { case .success: break @@ -354,7 +354,7 @@ private extension SidebarOutlineDataSource { } } } else { - destinationAccount.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in + destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in switch result { case .success: break @@ -365,8 +365,8 @@ private extension SidebarOutlineDataSource { } } - func moveFeedBetweenAccounts(node: Node, to parentNode: Node) { - guard let feed = node.representedObject as? Feed, + func moveWebFeedBetweenAccounts(node: Node, to parentNode: Node) { + guard let feed = node.representedObject as? WebFeed, let sourceAccount = nodeAccount(node), let sourceContainer = node.parent?.representedObject as? Container, let destinationAccount = nodeAccount(parentNode), @@ -374,13 +374,13 @@ private extension SidebarOutlineDataSource { return } - if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) { + if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) { BatchUpdate.shared.start() - destinationAccount.addFeed(existingFeed, to: destinationContainer) { result in + destinationAccount.addWebFeed(existingFeed, to: destinationContainer) { result in switch result { case .success: - sourceAccount.removeFeed(feed, from: sourceContainer) { result in + sourceAccount.removeWebFeed(feed, from: sourceContainer) { result in BatchUpdate.shared.end() switch result { case .success: @@ -398,10 +398,10 @@ private extension SidebarOutlineDataSource { } else { BatchUpdate.shared.start() - destinationAccount.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in + destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in switch result { case .success: - sourceAccount.removeFeed(feed, from: sourceContainer) { result in + sourceAccount.removeWebFeed(feed, from: sourceContainer) { result in BatchUpdate.shared.end() switch result { case .success: @@ -419,7 +419,7 @@ private extension SidebarOutlineDataSource { } } - func acceptLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set, _ parentNode: Node, _ index: Int) -> Bool { + func acceptLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set, _ parentNode: Node, _ index: Int) -> Bool { guard let draggedNodes = draggedNodes else { return false } @@ -427,15 +427,15 @@ private extension SidebarOutlineDataSource { draggedNodes.forEach { node in if sameAccount(node, parentNode) { if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false { - copyFeedInAccount(node: node, to: parentNode) + copyWebFeedInAccount(node: node, to: parentNode) } else { - moveFeedInAccount(node: node, to: parentNode) + moveWebFeedInAccount(node: node, to: parentNode) } } else { if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false { - copyFeedBetweenAccounts(node: node, to: parentNode) + copyWebFeedBetweenAccounts(node: node, to: parentNode) } else { - moveFeedBetweenAccounts(node: node, to: parentNode) + moveWebFeedBetweenAccounts(node: node, to: parentNode) } } } @@ -509,10 +509,10 @@ private extension SidebarOutlineDataSource { switch result { case .success(let destinationFolder): let group = DispatchGroup() - for feed in folder.topLevelFeeds { - if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) { + for feed in folder.topLevelWebFeeds { + if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) { group.enter() - destinationAccount.addFeed(existingFeed, to: destinationFolder) { result in + destinationAccount.addWebFeed(existingFeed, to: destinationFolder) { result in group.leave() switch result { case .success: @@ -523,7 +523,7 @@ private extension SidebarOutlineDataSource { } } else { group.enter() - destinationAccount.createFeed(url: feed.url, name: feed.editedName, container: destinationFolder) { result in + destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationFolder) { result in group.leave() switch result { case .success: @@ -563,7 +563,7 @@ private extension SidebarOutlineDataSource { return true } - func acceptSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ parentNode: Node, _ index: Int) -> Bool { + func acceptSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> Bool { guard nodeIsDropTarget(parentNode), index == NSOutlineViewDropOnItemIndex else { return false } @@ -580,12 +580,12 @@ private extension SidebarOutlineDataSource { return true } - func nodeHasChildRepresentingDraggedFeed(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Bool { + func nodeHasChildRepresentingDraggedFeed(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Bool { return nodeHasChildRepresentingAnyDraggedFeed(parentNode, Set([draggedFeed])) } - func nodeRepresentsAnyDraggedFeed(_ node: Node, _ draggedFeeds: Set) -> Bool { - guard let feed = node.representedObject as? Feed else { + func nodeRepresentsAnyDraggedFeed(_ node: Node, _ draggedFeeds: Set) -> Bool { + guard let feed = node.representedObject as? WebFeed else { return false } for draggedFeed in draggedFeeds { @@ -610,8 +610,8 @@ private extension SidebarOutlineDataSource { return account } else if let folder = node.representedObject as? Folder { return folder.account - } else if let feed = node.representedObject as? Feed { - return feed.account + } else if let webFeed = node.representedObject as? WebFeed { + return webFeed.account } else { return nil } @@ -622,7 +622,7 @@ private extension SidebarOutlineDataSource { return nodeAccount(node)?.accountID } - func nodeHasChildRepresentingAnyDraggedFeed(_ parentNode: Node, _ draggedFeeds: Set) -> Bool { + func nodeHasChildRepresentingAnyDraggedFeed(_ parentNode: Node, _ draggedFeeds: Set) -> Bool { for node in parentNode.childNodes { if nodeRepresentsAnyDraggedFeed(node, draggedFeeds) { return true @@ -631,11 +631,11 @@ private extension SidebarOutlineDataSource { return false } - func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Bool { + func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Bool { return violatesAccountSpecificBehavior(parentNode, Set([draggedFeed])) } - func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeeds: Set) -> Bool { + func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeeds: Set) -> Bool { if violatesDisallowFeedInRootFolder(parentNode) { return true } @@ -659,7 +659,7 @@ private extension SidebarOutlineDataSource { return false } - func violatesDisallowFeedCopyInRootFolder(_ parentNode: Node, _ draggedFeeds: Set) -> Bool { + func violatesDisallowFeedCopyInRootFolder(_ parentNode: Node, _ draggedFeeds: Set) -> Bool { guard let parentAccount = nodeAccount(parentNode), parentAccount.behaviors.contains(.disallowFeedCopyInRootFolder) else { return false } @@ -677,7 +677,7 @@ private extension SidebarOutlineDataSource { return false } - func indexWhereDraggedFeedWouldAppear(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Int { + func indexWhereDraggedFeedWouldAppear(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Int { let draggedFeedWrapper = PasteboardFeedObjectWrapper(pasteboardFeed: draggedFeed) let draggedFeedNode = Node(representedObject: draggedFeedWrapper, parent: nil) let nodes = parentNode.childNodes + [draggedFeedNode] @@ -706,9 +706,9 @@ final class PasteboardFeedObjectWrapper: DisplayNameProvider { var nameForDisplay: String { return pasteboardFeed.editedName ?? pasteboardFeed.name ?? "" } - let pasteboardFeed: PasteboardFeed + let pasteboardFeed: PasteboardWebFeed - init(pasteboardFeed: PasteboardFeed) { + init(pasteboardFeed: PasteboardWebFeed) { self.pasteboardFeed = pasteboardFeed } } diff --git a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift index 49674cb19..2e1b24f57 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift @@ -26,8 +26,8 @@ extension SidebarViewController { let object = objects.first! switch object { - case is Feed: - return menuForFeed(object as! Feed) + case is WebFeed: + return menuForWebFeed(object as! WebFeed) case is Folder: return menuForFolder(object as! Folder) case is PseudoFeed: @@ -83,7 +83,7 @@ extension SidebarViewController { @objc func renameFromContextualMenu(_ sender: Any?) { - guard let window = view.window, let menuItem = sender as? NSMenuItem, let object = menuItem.representedObject as? DisplayNameProvider, object is Feed || object is Folder else { + guard let window = view.window, let menuItem = sender as? NSMenuItem, let object = menuItem.representedObject as? DisplayNameProvider, object is WebFeed || object is Folder else { return } @@ -99,7 +99,7 @@ extension SidebarViewController: RenameWindowControllerDelegate { func renameWindowController(_ windowController: RenameWindowController, didRenameObject object: Any, withNewName name: String) { - if let feed = object as? Feed { + if let feed = object as? WebFeed { feed.rename(to: name) { result in switch result { case .success: @@ -135,32 +135,32 @@ private extension SidebarViewController { return menu } - func menuForFeed(_ feed: Feed) -> NSMenu? { + func menuForWebFeed(_ webFeed: WebFeed) -> NSMenu? { let menu = NSMenu(title: "") - if feed.unreadCount > 0 { - menu.addItem(markAllReadMenuItem([feed])) + if webFeed.unreadCount > 0 { + menu.addItem(markAllReadMenuItem([webFeed])) menu.addItem(NSMenuItem.separator()) } - if let homePageURL = feed.homePageURL, let _ = URL(string: homePageURL) { + if let homePageURL = webFeed.homePageURL, let _ = URL(string: homePageURL) { let item = menuItem(NSLocalizedString("Open Home Page", comment: "Command"), #selector(openHomePageFromContextualMenu(_:)), homePageURL) menu.addItem(item) menu.addItem(NSMenuItem.separator()) } - let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), feed.url) + let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), webFeed.url) menu.addItem(copyFeedURLItem) - if let homePageURL = feed.homePageURL { + if let homePageURL = webFeed.homePageURL { let item = menuItem(NSLocalizedString("Copy Home Page URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), homePageURL) menu.addItem(item) } menu.addItem(NSMenuItem.separator()) - menu.addItem(renameMenuItem(feed)) - menu.addItem(deleteMenuItem([feed])) + menu.addItem(renameMenuItem(webFeed)) + menu.addItem(deleteMenuItem([webFeed])) return menu } @@ -245,7 +245,7 @@ private extension SidebarViewController { func objectIsFeedOrFolder(_ object: Any) -> Bool { - return object is Feed || object is Folder + return object is WebFeed || object is Folder } func menuItem(_ title: String, _ action: Selector, _ representedObject: Any) -> NSMenuItem { diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index f1d19437f..ba74c57be 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -23,7 +23,7 @@ protocol SidebarDelegate: class { weak var delegate: SidebarDelegate? - let treeControllerDelegate = FeedTreeControllerDelegate() + let treeControllerDelegate = WebFeedTreeControllerDelegate() lazy var treeController: TreeController = { return TreeController(delegate: treeControllerDelegate) }() @@ -49,7 +49,7 @@ protocol SidebarDelegate: class { outlineView.dataSource = dataSource outlineView.doubleAction = #selector(doubleClickedSidebar(_:)) outlineView.setDraggingSourceOperationMask([.move, .copy], forLocal: true) - outlineView.registerForDraggedTypes([FeedPasteboardWriter.feedUTIInternalType, FeedPasteboardWriter.feedUTIType, .URL, .string]) + outlineView.registerForDraggedTypes([WebFeedPasteboardWriter.webFeedUTIInternalType, WebFeedPasteboardWriter.webFeedUTIType, .URL, .string]) NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil) @@ -59,7 +59,7 @@ protocol SidebarDelegate: class { NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidRequestSidebarSelection(_:)), name: .UserDidRequestSidebarSelection, object: nil) @@ -110,7 +110,7 @@ protocol SidebarDelegate: class { } @objc func userDidAddFeed(_ notification: Notification) { - guard let feed = notification.userInfo?[UserInfoKey.feed] else { + guard let feed = notification.userInfo?[UserInfoKey.webFeed] else { return } revealAndSelectRepresentedObject(feed as AnyObject) @@ -120,12 +120,12 @@ protocol SidebarDelegate: class { applyToAvailableCells(configureFavicon) } - @objc func feedSettingDidChange(_ note: Notification) { - guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else { + @objc func webFeedSettingDidChange(_ note: Notification) { + guard let webFeed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else { return } - if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL { - configureCellsForRepresentedObject(feed) + if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL { + configureCellsForRepresentedObject(webFeed) } } @@ -140,7 +140,7 @@ protocol SidebarDelegate: class { } @objc func userDidRequestSidebarSelection(_ note: Notification) { - guard let feed = note.userInfo?[UserInfoKey.feed] else { + guard let feed = note.userInfo?[UserInfoKey.webFeed] else { return } revealAndSelectRepresentedObject(feed as AnyObject) @@ -370,11 +370,11 @@ private extension SidebarViewController { return selectedNodes.first! } - var singleSelectedFeed: Feed? { + var singleSelectedFeed: WebFeed? { guard let node = singleSelectedNode else { return nil } - return node.representedObject as? Feed + return node.representedObject as? WebFeed } func rebuildTreeAndReloadDataIfNeeded() { @@ -413,11 +413,11 @@ private extension SidebarViewController { // For feeds, actually fetch from database. for object in objects { - if let feed = object as? Feed, let account = feed.account { + if let feed = object as? WebFeed, let account = feed.account { account.updateUnreadCounts(for: Set([feed])) } else if let folder = object as? Folder, let account = folder.account { - account.updateUnreadCounts(for: folder.flattenedFeeds()) + account.updateUnreadCounts(for: folder.flattenedWebFeeds()) } } } @@ -504,10 +504,10 @@ private extension SidebarViewController { } func findFeedNode(_ userInfo: [AnyHashable : Any]?, beginningAt startingNode: Node) -> Node? { - guard let feedID = userInfo?[ArticlePathKey.feedID] as? String else { + guard let webFeedID = userInfo?[ArticlePathKey.webFeedID] as? String else { return nil } - if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) { + if let node = startingNode.descendantNode(where: { ($0.representedObject as? WebFeed)?.webFeedID == webFeedID }) { return node } return nil @@ -620,7 +620,7 @@ private extension Node { if representedObject === object { return true } - if let feed1 = object as? Feed, let feed2 = representedObject as? Feed { + if let feed1 = object as? WebFeed, let feed2 = representedObject as? WebFeed { return feed1 == feed2 } return false diff --git a/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift b/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift index 5d6b9493d..002b44c6d 100644 --- a/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift +++ b/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift @@ -91,7 +91,7 @@ private extension ArticlePasteboardWriter { s += "Date: \(article.logicalDatePublished)\n\n" - if let feed = article.feed { + if let feed = article.webFeed { s += "Feed: \(feed.nameForDisplay)\n" if let homePageURL = feed.homePageURL { s += "Home page: \(homePageURL)\n" @@ -106,7 +106,7 @@ private extension ArticlePasteboardWriter { static let articleID = "articleID" // database ID, unique per account static let uniqueID = "uniqueID" // unique ID, unique per feed (guid, or possibly calculated) static let feedURL = "feedURL" - static let feedID = "feedID" // may differ from feedURL if coming from a syncing system + static let webFeedID = "webFeedID" // may differ from feedURL if coming from a syncing system static let title = "title" static let contentHTML = "contentHTML" static let contentText = "contentText" @@ -147,11 +147,11 @@ private extension ArticlePasteboardWriter { d[Key.articleID] = article.articleID d[Key.uniqueID] = article.uniqueID - if let feed = article.feed { + if let feed = article.webFeed { d[Key.feedURL] = feed.url } - d[Key.feedID] = article.feedID + d[Key.webFeedID] = article.webFeedID d[Key.title] = article.title ?? nil d[Key.contentHTML] = article.contentHTML ?? nil d[Key.contentText] = article.contentText ?? nil diff --git a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift index f3a8fa2fc..4b5107b8a 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift @@ -74,12 +74,12 @@ extension TimelineViewController { @objc func selectFeedInSidebarFromContextualMenu(_ sender: Any?) { - guard let menuItem = sender as? NSMenuItem, let feed = menuItem.representedObject as? Feed else { + guard let menuItem = sender as? NSMenuItem, let feed = menuItem.representedObject as? WebFeed else { return } var userInfo = UserInfoDictionary() - userInfo[UserInfoKey.feed] = feed + userInfo[UserInfoKey.webFeed] = feed NotificationCenter.default.post(name: .UserDidRequestSidebarSelection, object: self, userInfo: userInfo) @@ -174,7 +174,7 @@ private extension TimelineViewController { menu.addSeparatorIfNeeded() - if articles.count == 1, let feed = articles.first!.feed { + if articles.count == 1, let feed = articles.first!.webFeed { menu.addItem(selectFeedInSidebarMenuItem(feed)) if let markAllMenuItem = markAllAsReadMenuItem(feed) { menu.addItem(markAllMenuItem) @@ -247,13 +247,13 @@ private extension TimelineViewController { return menuItem(NSLocalizedString("Mark Older as Read", comment: "Command"), #selector(markOlderArticlesReadFromContextualMenu(_:)), articles) } - func selectFeedInSidebarMenuItem(_ feed: Feed) -> NSMenuItem { + func selectFeedInSidebarMenuItem(_ feed: WebFeed) -> NSMenuItem { let localizedMenuText = NSLocalizedString("Select “%@” in Sidebar", comment: "Command") let formattedMenuText = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) return menuItem(formattedMenuText as String, #selector(selectFeedInSidebarFromContextualMenu(_:)), feed) } - func markAllAsReadMenuItem(_ feed: Feed) -> NSMenuItem? { + func markAllAsReadMenuItem(_ feed: WebFeed) -> NSMenuItem? { let articles = Array(feed.fetchArticles()) guard articles.canMarkAllAsRead() else { diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index f4ab509e8..54ff1e9e9 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -25,7 +25,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr if !representedObjectArraysAreEqual(oldValue, representedObjects) { unreadCount = 0 if let representedObjects = representedObjects { - if representedObjects.count == 1 && representedObjects.first is Feed { + if representedObjects.count == 1 && representedObjects.first is WebFeed { showFeedNames = false } else { @@ -168,7 +168,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr if !didRegisterForNotifications { NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil) @@ -437,15 +437,15 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr updateUnreadCount() } - @objc func feedIconDidBecomeAvailable(_ note: Notification) { - guard showIcons, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { + @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { + guard showIcons, let feed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else { return } let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in guard let article = articles.articleAtRow(row) else { return false } - return feed == article.feed + return feed == article.webFeed } if let indexesToReload = indexesToReload { reloadCells(for: indexesToReload) @@ -480,11 +480,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func accountDidDownloadArticles(_ note: Notification) { - guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set else { + guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set else { return } - let shouldFetchAndMergeArticles = representedObjectsContainsAnyFeed(feeds) || representedObjectsContainsAnyPseudoFeed() + let shouldFetchAndMergeArticles = representedObjectsContainsAnyWebFeed(feeds) || representedObjectsContainsAnyPseudoFeed() if shouldFetchAndMergeArticles { queueFetchAndMergeArticles() } @@ -568,7 +568,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr 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, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status) + 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, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status) let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: showingFeedNames, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil) let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance) @@ -717,7 +717,7 @@ extension TimelineViewController: NSTableViewDelegate { private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) { cell.objectValue = article let iconImage = article.iconImage() - cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, iconImage: iconImage, showIcon: showIcons, featuredImage: nil) + cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, iconImage: iconImage, showIcon: showIcons, featuredImage: nil) } private func iconFor(_ article: Article) -> IconImage? { @@ -733,11 +733,11 @@ extension TimelineViewController: NSTableViewDelegate { } } - guard let feed = article.feed else { + guard let feed = article.webFeed else { return nil } - if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) { + if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: feed) { return feedIcon } @@ -1056,23 +1056,23 @@ private extension TimelineViewController { return representedObjects?.contains(where: { $0 is Folder }) ?? false } - func representedObjectsContainsAnyFeed(_ feeds: Set) -> Bool { + func representedObjectsContainsAnyWebFeed(_ webFeeds: Set) -> Bool { // Return true if there’s a match or if a folder contains (recursively) one of feeds guard let representedObjects = representedObjects else { return false } for representedObject in representedObjects { - if let feed = representedObject as? Feed { - for oneFeed in feeds { - if feed.feedID == oneFeed.feedID || feed.url == oneFeed.url { + if let feed = representedObject as? WebFeed { + for oneFeed in webFeeds { + if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url { return true } } } else if let folder = representedObject as? Folder { - for oneFeed in feeds { - if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) { + for oneFeed in webFeeds { + if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) { return true } } diff --git a/Mac/Scriptability/Account+Scriptability.swift b/Mac/Scriptability/Account+Scriptability.swift index 249db4523..c2cc7667a 100644 --- a/Mac/Scriptability/Account+Scriptability.swift +++ b/Mac/Scriptability/Account+Scriptability.swift @@ -81,7 +81,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta } else { container = account } - account.removeFeed(scriptableFeed.feed, from: container!) { result in + account.removeWebFeed(scriptableFeed.webFeed, from: container!) { result in } } } @@ -96,19 +96,19 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta @objc(feeds) var feeds:NSArray { - return account.topLevelFeeds.map { ScriptableFeed($0, container:self) } as NSArray + return account.topLevelWebFeeds.map { ScriptableFeed($0, container:self) } as NSArray } @objc(valueInFeedsWithUniqueID:) func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? { - let feeds = Array(account.topLevelFeeds) - guard let feed = feeds.first(where:{$0.feedID == id}) else { return nil } + let feeds = Array(account.topLevelWebFeeds) + guard let feed = feeds.first(where:{$0.webFeedID == id}) else { return nil } return ScriptableFeed(feed, container:self) } @objc(valueInFeedsWithName:) func valueInFeeds(withName name:String) -> ScriptableFeed? { - let feeds = Array(account.topLevelFeeds) + let feeds = Array(account.topLevelWebFeeds) guard let feed = feeds.first(where:{$0.name == name}) else { return nil } return ScriptableFeed(feed, container:self) } @@ -134,13 +134,13 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta @objc(allFeeds) var allFeeds: NSArray { var feeds = [ScriptableFeed]() - for feed in account.topLevelFeeds { + for feed in account.topLevelWebFeeds { feeds.append(ScriptableFeed(feed, container: self)) } if let folders = account.folders { for folder in folders { let scriptableFolder = ScriptableFolder(folder, container: self) - for feed in folder.topLevelFeeds { + for feed in folder.topLevelWebFeeds { feeds.append(ScriptableFeed(feed, container: scriptableFolder)) } } diff --git a/Mac/Scriptability/Feed+Scriptability.swift b/Mac/Scriptability/Feed+Scriptability.swift index fd85fe525..caed94af9 100644 --- a/Mac/Scriptability/Feed+Scriptability.swift +++ b/Mac/Scriptability/Feed+Scriptability.swift @@ -14,11 +14,11 @@ import Articles @objc(ScriptableFeed) class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer { - let feed:Feed + let webFeed:WebFeed let container:ScriptingObjectContainer - init (_ feed:Feed, container:ScriptingObjectContainer) { - self.feed = feed + init (_ feed:WebFeed, container:ScriptingObjectContainer) { + self.webFeed = feed self.container = container } @@ -45,7 +45,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine // but in either case it seems like the accountID would be used as the keydata, so I chose ID @objc(uniqueId) var scriptingUniqueId:Any { - return feed.feedID + return webFeed.webFeedID } // MARK: --- ScriptingObjectContainer protocol --- @@ -71,7 +71,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine return url } - class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableFeed { + class func scriptableFeed(_ feed:WebFeed, account:Account, folder:Folder?) -> ScriptableFeed { let scriptableAccount = ScriptableAccount(account) if let folder = folder { let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount) @@ -88,7 +88,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine let (account, folder) = command.accountAndFolderForNewChild() guard let url = self.urlForNewFeed(arguments:arguments) else {return nil} - if let existingFeed = account.existingFeed(withURL:url) { + if let existingFeed = account.existingWebFeed(withURL:url) { return scriptableFeed(existingFeed, account:account, folder:folder).objectSpecifier } @@ -102,10 +102,10 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine // suspendExecution(). When we get the callback, we supply the event result and call resumeExecution(). command.suspendExecution() - account.createFeed(url: url, name: titleFromArgs, container: container) { result in + account.createWebFeed(url: url, name: titleFromArgs, container: container) { result in switch result { case .success(let feed): - NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) + NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed]) let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder) command.resumeExecution(withResult:scriptableFeed.objectSpecifier) case .failure: @@ -121,51 +121,51 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine @objc(url) var url:String { - return self.feed.url + return self.webFeed.url } @objc(name) var name:String { - return self.feed.name ?? "" + return self.webFeed.name ?? "" } @objc(homePageURL) var homePageURL:String { - return self.feed.homePageURL ?? "" + return self.webFeed.homePageURL ?? "" } @objc(iconURL) var iconURL:String { - return self.feed.iconURL ?? "" + return self.webFeed.iconURL ?? "" } @objc(faviconURL) var faviconURL:String { - return self.feed.faviconURL ?? "" + return self.webFeed.faviconURL ?? "" } @objc(opmlRepresentation) var opmlRepresentation:String { - return self.feed.OPMLString(indentLevel:0, strictConformance: true) + return self.webFeed.OPMLString(indentLevel:0, strictConformance: true) } // MARK: --- scriptable elements --- @objc(authors) var authors:NSArray { - let feedAuthors = feed.authors ?? [] + let feedAuthors = webFeed.authors ?? [] return feedAuthors.map { ScriptableAuthor($0, container:self) } as NSArray } @objc(valueInAuthorsWithUniqueID:) func valueInAuthors(withUniqueID id:String) -> ScriptableAuthor? { - guard let author = feed.authors?.first(where:{$0.authorID == id}) else { return nil } + guard let author = webFeed.authors?.first(where:{$0.authorID == id}) else { return nil } return ScriptableAuthor(author, container:self) } @objc(articles) var articles:NSArray { - let feedArticles = feed.fetchArticles() + let feedArticles = webFeed.fetchArticles() // the articles are a set, use the sorting algorithm from the viewer let sortedArticles = feedArticles.sorted(by:{ return $0.logicalDatePublished > $1.logicalDatePublished @@ -175,7 +175,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine @objc(valueInArticlesWithUniqueID:) func valueInArticles(withUniqueID id:String) -> ScriptableArticle? { - let articles = feed.fetchArticles() + let articles = webFeed.fetchArticles() guard let article = articles.first(where:{$0.uniqueID == id}) else { return nil } return ScriptableArticle(article, container:self) } diff --git a/Mac/Scriptability/Folder+Scriptability.swift b/Mac/Scriptability/Folder+Scriptability.swift index 7990d8384..406f04490 100644 --- a/Mac/Scriptability/Folder+Scriptability.swift +++ b/Mac/Scriptability/Folder+Scriptability.swift @@ -53,7 +53,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai func deleteElement(_ element:ScriptingObject) { if let scriptableFeed = element as? ScriptableFeed { BatchUpdate.shared.perform { - folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in } + folder.account?.removeWebFeed(scriptableFeed.webFeed, from: folder) { result in } } } } @@ -97,7 +97,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai @objc(feeds) var feeds:NSArray { - let feeds = Array(folder.topLevelFeeds) + let feeds = Array(folder.topLevelWebFeeds) return feeds.map { ScriptableFeed($0, container:self) } as NSArray } diff --git a/Mac/Scriptability/NSApplication+Scriptability.swift b/Mac/Scriptability/NSApplication+Scriptability.swift index a7e181009..90f9aaffe 100644 --- a/Mac/Scriptability/NSApplication+Scriptability.swift +++ b/Mac/Scriptability/NSApplication+Scriptability.swift @@ -30,7 +30,7 @@ extension NSApplication : ScriptingObjectContainer { func currentArticle() -> ScriptableArticle? { var scriptableArticle: ScriptableArticle? if let currentArticle = appDelegate.scriptingCurrentArticle { - if let feed = currentArticle.feed { + if let feed = currentArticle.webFeed { let scriptableFeed = ScriptableFeed(feed, container:self) scriptableArticle = ScriptableArticle(currentArticle, container:scriptableFeed) } @@ -42,7 +42,7 @@ extension NSApplication : ScriptingObjectContainer { func selectedArticles() -> NSArray { let articles = appDelegate.scriptingSelectedArticles let scriptableArticles:[ScriptableArticle] = articles.compactMap { article in - if let feed = article.feed { + if let feed = article.webFeed { let scriptableFeed = ScriptableFeed(feed, container:self) return ScriptableArticle(article, container:scriptableFeed) } else { @@ -73,11 +73,11 @@ extension NSApplication : ScriptingObjectContainer { for 'articles of feed "The Shape of Everything" of account "On My Mac"' */ - func allFeeds() -> [Feed] { + func allFeeds() -> [WebFeed] { let accounts = AccountManager.shared.activeAccounts - let emptyFeeds:[Feed] = [] - return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [Feed] in - let accountFeeds = Array(nthAccount.topLevelFeeds) + let emptyFeeds:[WebFeed] = [] + return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [WebFeed] in + let accountFeeds = Array(nthAccount.topLevelWebFeeds) return result + accountFeeds } } @@ -91,7 +91,7 @@ extension NSApplication : ScriptingObjectContainer { @objc(valueInFeedsWithUniqueID:) func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? { let feeds = self.allFeeds() - guard let feed = feeds.first(where:{$0.feedID == id}) else { return nil } + guard let feed = feeds.first(where:{$0.webFeedID == id}) else { return nil } return ScriptableFeed(feed, container:self) } } diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index d37b13a97..8bac9c20b 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -34,14 +34,14 @@ 512AF9C2236ED52C0066F8BE /* InspectorImageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512AF9C1236ED52C0066F8BE /* InspectorImageHeaderView.swift */; }; 512AF9DD236F05230066F8BE /* InteractiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512AF9DC236F05230066F8BE /* InteractiveLabel.swift */; }; 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; - 512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; }; + 512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; }; 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.swift */; }; 512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */; }; 512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; 5131463E235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 51314668235A7E4600387FDC /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51314666235A7E4600387FDC /* IntentHandler.swift */; }; - 513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */; }; - 513146B3235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */; }; + 513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */; }; + 513146B3235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */; }; 513146B4235A8FD000387FDC /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */; }; 513146B6235A8FD000387FDC /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */; }; 513146B8235A8FD000387FDC /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; }; @@ -67,7 +67,7 @@ 513C5D0A232574D2003D4054 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */; }; 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 */; }; + 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 */; }; @@ -171,7 +171,7 @@ 51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4527E2265092C00C03939 /* ArticleViewController.swift */; }; 51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; }; 51C452862265093600C03939 /* Add.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51C452822265093600C03939 /* Add.storyboard */; }; - 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452842265093600C03939 /* AddFeedViewController.swift */; }; + 51C452882265093600C03939 /* AddWebFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452842265093600C03939 /* AddWebFeedViewController.swift */; }; 51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4528B2265095F00C03939 /* AddFolderViewController.swift */; }; 51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; @@ -189,7 +189,7 @@ 51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; - 51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; }; + 51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; }; 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; }; @@ -290,11 +290,11 @@ 65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */; }; 65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A979E1ED9F130007D329B /* SidebarCell.swift */; }; 65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; - 65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; }; + 65ED3FDB235DEF6C0081F399 /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; }; 65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97631ED9EB96007D329B /* UnreadCountView.swift */; }; 65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; 65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */; }; - 65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; }; + 65ED3FDF235DEF6C0081F399 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; }; 65ED3FE0235DEF6C0081F399 /* AccountsControlsBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7122629E1200D921D6 /* AccountsControlsBackgroundView.swift */; }; 65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; }; 65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; }; @@ -320,7 +320,7 @@ 65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; 65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; }; 65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; - 65ED3FFA235DEF6C0081F399 /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; }; + 65ED3FFA235DEF6C0081F399 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */; }; 65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; }; 65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */; }; 65ED3FFD235DEF6C0081F399 /* PasteboardFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */; }; @@ -346,7 +346,7 @@ 65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; }; 65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */; }; 65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B661FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift */; }; - 65ED4014235DEF6C0081F399 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.swift */; }; + 65ED4014235DEF6C0081F399 /* PasteboardWebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */; }; 65ED4015235DEF6C0081F399 /* AccountsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */; }; 65ED4016235DEF6C0081F399 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977E1ED9EC42007D329B /* DetailViewController.swift */; }; 65ED4017235DEF6C0081F399 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6622629B3900D921D6 /* AppDelegate.swift */; }; @@ -469,7 +469,7 @@ 841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; }; 841ABA6020145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */; }; 84216D0322128B9D0049B9B9 /* DetailWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */; }; - 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; }; + 8426118A1FCB67AA0086A189 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; }; 8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; 842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; @@ -489,7 +489,7 @@ 845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; }; 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; - 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; }; + 8472058120142E8900AD578B /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */; }; 8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; 847CD6CA232F4CBF00FAC46D /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; }; 847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */; }; @@ -501,7 +501,7 @@ 8483630B2262A3F000DA1D35 /* RenameSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363092262A3F000DA1D35 /* RenameSheet.xib */; }; 8483630E2262A3FE00DA1D35 /* MainWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8483630C2262A3FE00DA1D35 /* MainWindow.storyboard */; }; 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; }; - 848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.swift */; }; + 848D578E21543519005FFAD5 /* PasteboardWebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */; }; 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; }; 849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; }; @@ -510,7 +510,7 @@ 849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; 849A975E1ED9EB72007D329B /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A975D1ED9EB72007D329B /* MainWindowController.swift */; }; 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */; }; - 849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; }; + 849A97651ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; }; 849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97621ED9EB96007D329B /* SidebarViewController.swift */; }; 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97631ED9EB96007D329B /* UnreadCountView.swift */; }; 849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */; }; @@ -1228,7 +1228,7 @@ 51314665235A7E4600387FDC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51314666235A7E4600387FDC /* IntentHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; 51314684235A7EB900387FDC /* NetNewsWire_iOS_IntentsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_IntentsExtension.entitlements; sourceTree = ""; }; - 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedIntentHandler.swift; sourceTree = ""; }; + 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedIntentHandler.swift; sourceTree = ""; }; 51314706235C41FC00387FDC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; 51314714235C420900387FDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = ""; }; 513228F2233037620033D4ED /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; @@ -1236,7 +1236,7 @@ 513C5CE8232571C2003D4054 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; 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 = ""; }; + 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.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 = ""; }; @@ -1308,7 +1308,7 @@ 51C4527E2265092C00C03939 /* ArticleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleViewController.swift; sourceTree = ""; }; 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlattenedAccountFolderPickerData.swift; sourceTree = ""; }; 51C452822265093600C03939 /* Add.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Add.storyboard; sourceTree = ""; }; - 51C452842265093600C03939 /* AddFeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFeedViewController.swift; sourceTree = ""; }; + 51C452842265093600C03939 /* AddWebFeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddWebFeedViewController.swift; sourceTree = ""; }; 51C4528B2265095F00C03939 /* AddFolderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderViewController.swift; sourceTree = ""; }; 51C452B32265141B00C03939 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; 51C452B72265178500C03939 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; @@ -1383,7 +1383,7 @@ 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Articles.xcodeproj; path = ../Frameworks/Articles/Articles.xcodeproj; sourceTree = ""; }; 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ArticlesDatabase.xcodeproj; path = ../Frameworks/ArticlesDatabase/ArticlesDatabase.xcodeproj; sourceTree = ""; }; 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebViewController.swift; sourceTree = ""; }; - 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconDownloader.swift; sourceTree = ""; }; + 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedIconDownloader.swift; sourceTree = ""; }; 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadataDownloader.swift; sourceTree = ""; }; 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedImageDownloader.swift; sourceTree = ""; }; 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSHTMLMetadata+Extension.swift"; sourceTree = ""; }; @@ -1406,7 +1406,7 @@ 845EE7C01FC2488C00854A1F /* SmartFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeed.swift; sourceTree = ""; }; 846E77301F6EF5D600A165E2 /* Account.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Account.xcodeproj; path = ../Frameworks/Account/Account.xcodeproj; sourceTree = ""; }; 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkStatusCommand.swift; sourceTree = ""; }; - 8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = ""; }; + 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = ""; }; 847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedDelegate.swift; sourceTree = ""; }; 847CD6C9232F4CBF00FAC46D /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; @@ -1419,7 +1419,7 @@ 8483630A2262A3F000DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/RenameSheet.xib; sourceTree = SOURCE_ROOT; }; 8483630D2262A3FE00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Mac/Base.lproj/MainWindow.storyboard; sourceTree = SOURCE_ROOT; }; 848B937121C8C5540038DC0D /* CrashReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReporter.swift; sourceTree = ""; }; - 848D578D21543519005FFAD5 /* PasteboardFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardFeed.swift; sourceTree = ""; }; + 848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardWebFeed.swift; sourceTree = ""; }; 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconDownloader.swift; sourceTree = ""; }; 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderWindowController.swift; sourceTree = ""; }; 849A97511ED9EAC0007D329B /* AddFeedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedController.swift; path = AddFeed/AddFeedController.swift; sourceTree = ""; }; @@ -1428,7 +1428,7 @@ 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultFeedsImporter.swift; sourceTree = ""; }; 849A975D1ED9EB72007D329B /* MainWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = ""; }; 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarOutlineView.swift; sourceTree = ""; }; - 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedTreeControllerDelegate.swift; sourceTree = ""; }; + 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebFeedTreeControllerDelegate.swift; sourceTree = ""; }; 849A97621ED9EB96007D329B /* SidebarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarViewController.swift; sourceTree = ""; }; 849A97631ED9EB96007D329B /* UnreadCountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountView.swift; sourceTree = ""; }; 849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineTableRowView.swift; sourceTree = ""; }; @@ -1688,7 +1688,7 @@ children = ( 516A09412361248000EAE89B /* Inspector.storyboard */, 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */, - 5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */, + 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */, 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */, 512AF9C1236ED52C0066F8BE /* InspectorImageHeaderView.swift */, ); @@ -1707,7 +1707,7 @@ isa = PBXGroup; children = ( 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */, - 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */, + 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */, 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */, ); path = Tree; @@ -1717,7 +1717,7 @@ isa = PBXGroup; children = ( 51314707235C41FC00387FDC /* Intents.intentdefinition */, - 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */, + 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */, ); path = Intents; sourceTree = ""; @@ -1925,7 +1925,7 @@ 51C452822265093600C03939 /* Add.storyboard */, 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */, 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */, - 51C452842265093600C03939 /* AddFeedViewController.swift */, + 51C452842265093600C03939 /* AddWebFeedViewController.swift */, 51C4528B2265095F00C03939 /* AddFolderViewController.swift */, ); path = Add; @@ -2119,7 +2119,7 @@ children = ( 845213221FCA5B10003B6E93 /* ImageDownloader.swift */, 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */, - 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */, + 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */, 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */, 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */, ); @@ -2210,7 +2210,7 @@ 84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */, 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */, 849A97631ED9EB96007D329B /* UnreadCountView.swift */, - 848D578D21543519005FFAD5 /* PasteboardFeed.swift */, + 848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */, 84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */, 849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */, 844B5B6A1FEA224000C7C76A /* Keyboard */, @@ -2340,7 +2340,7 @@ children = ( 84BBB12B20142A4700F054F5 /* Inspector.storyboard */, 84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */, - 8472058020142E8900AD578B /* FeedInspectorViewController.swift */, + 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */, 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */, 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */, 841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */, @@ -3679,7 +3679,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 513146B3235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */, + 513146B3235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */, 51314705235C41FC00387FDC /* Intents.intentdefinition in Sources */, 51314668235A7E4600387FDC /* IntentHandler.swift in Sources */, ); @@ -3753,11 +3753,11 @@ 65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */, 65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */, 65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */, - 65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */, + 65ED3FDB235DEF6C0081F399 /* WebFeedTreeControllerDelegate.swift in Sources */, 65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */, 65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */, 65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */, - 65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */, + 65ED3FDF235DEF6C0081F399 /* WebFeedIconDownloader.swift in Sources */, 65ED3FE0235DEF6C0081F399 /* AccountsControlsBackgroundView.swift in Sources */, 65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */, 65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */, @@ -3784,7 +3784,7 @@ 65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */, 65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */, 65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */, - 65ED3FFA235DEF6C0081F399 /* FeedInspectorViewController.swift in Sources */, + 65ED3FFA235DEF6C0081F399 /* WebFeedInspectorViewController.swift in Sources */, 65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */, 65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */, 65ED3FFD235DEF6C0081F399 /* PasteboardFolder.swift in Sources */, @@ -3810,7 +3810,7 @@ 65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */, 65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */, 65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */, - 65ED4014235DEF6C0081F399 /* PasteboardFeed.swift in Sources */, + 65ED4014235DEF6C0081F399 /* PasteboardWebFeed.swift in Sources */, 65ED4015235DEF6C0081F399 /* AccountsDetailViewController.swift in Sources */, 65ED4016235DEF6C0081F399 /* DetailViewController.swift in Sources */, 65ED4017235DEF6C0081F399 /* AppDelegate.swift in Sources */, @@ -3873,7 +3873,7 @@ files = ( 840D617F2029031C009BC708 /* AppDelegate.swift in Sources */, 51236339236915B100951F16 /* RoundedProgressView.swift in Sources */, - 512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */, + 512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */, 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */, 51EF0F79227716380050506E /* ColorHash.swift in Sources */, 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */, @@ -3891,7 +3891,7 @@ 517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, 51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */, - 513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */, + 513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */, 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */, 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, @@ -3920,7 +3920,7 @@ 5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */, 512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */, 51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */, - 51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */, + 51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */, 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */, 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */, 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */, @@ -3945,7 +3945,7 @@ 51C452AF2265108300C03939 /* ArticleArray.swift in Sources */, 51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */, 51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */, - 5141E7392373C18B0013FF27 /* FeedInspectorViewController.swift in Sources */, + 5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */, 5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */, 84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */, 51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */, @@ -3964,7 +3964,7 @@ 51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */, 51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */, 5108F6D823763094001ABC45 /* TickMarkSlider.swift in Sources */, - 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */, + 51C452882265093600C03939 /* AddWebFeedViewController.swift in Sources */, 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */, 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */, 5108F6B72375E612001ABC45 /* CacheCleaner.swift in Sources */, @@ -4040,11 +4040,11 @@ 8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, - 849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */, + 849A97651ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift in Sources */, 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 51FE10092346739D0056195D /* ActivityType.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, - 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */, + 8426118A1FCB67AA0086A189 /* WebFeedIconDownloader.swift in Sources */, 84C9FC7B22629E1200D921D6 /* AccountsControlsBackgroundView.swift in Sources */, 84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */, 84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */, @@ -4070,7 +4070,7 @@ 8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */, 51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */, 51FE100A234673A00056195D /* ActivityManager.swift in Sources */, - 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */, + 8472058120142E8900AD578B /* WebFeedInspectorViewController.swift in Sources */, 55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */, 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */, 84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */, @@ -4098,7 +4098,7 @@ 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */, 8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */, 844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */, - 848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */, + 848D578E21543519005FFAD5 /* PasteboardWebFeed.swift in Sources */, 5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */, 849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */, 518C3193237B00D9004D740F /* ArticleIconSchemeHandler.swift in Sources */, diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift index 276dc46e8..dc1b216a8 100644 --- a/Shared/Activity/ActivityManager.swift +++ b/Shared/Activity/ActivityManager.swift @@ -29,7 +29,7 @@ class ActivityManager { } init() { - NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) } func invalidateCurrentActivities() { @@ -43,7 +43,7 @@ class ActivityManager { selectingActivity = makeSelectFeedActivity(fetcher: fetcher) - if let feed = fetcher as? Feed { + if let feed = fetcher as? WebFeed { updateSelectingActivityFeedSearchAttributes(with: feed) } @@ -106,8 +106,8 @@ class ActivityManager { } } - for feed in account.flattenedFeeds() { - ids.append(contentsOf: identifers(for: feed)) + for webFeed in account.flattenedWebFeeds() { + ids.append(contentsOf: identifers(for: webFeed)) } CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids) @@ -117,31 +117,31 @@ class ActivityManager { var ids = [String]() ids.append(identifer(for: folder)) - for feed in folder.flattenedFeeds() { - ids.append(contentsOf: identifers(for: feed)) + for webFeed in folder.flattenedWebFeeds() { + ids.append(contentsOf: identifers(for: webFeed)) } CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids) } - static func cleanUp(_ feed: Feed) { - CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifers(for: feed)) + static func cleanUp(_ webFeed: WebFeed) { + CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifers(for: webFeed)) } #endif - @objc func feedIconDidBecomeAvailable(_ note: Notification) { - guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else { + @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { + guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.webFeedID] as? String else { return } #if os(iOS) - if let article = readingArticle, activityFeedId == article.feedID { + if let article = readingArticle, activityFeedId == article.webFeedID { updateReadArticleSearchAttributes(with: article) } #endif - if activityFeedId == feed.feedID { - updateSelectingActivityFeedSearchAttributes(with: feed) + if activityFeedId == webFeed.webFeedID { + updateSelectingActivityFeedSearchAttributes(with: webFeed) } } @@ -224,7 +224,7 @@ private extension ActivityManager { #endif func makeKeywords(_ article: Article) -> [String] { - let feedNameKeywords = makeKeywords(article.feed?.nameForDisplay) + let feedNameKeywords = makeKeywords(article.webFeed?.nameForDisplay) let articleTitleKeywords = makeKeywords(ArticleStringFormatter.truncatedTitle(article)) return feedNameKeywords + articleTitleKeywords } @@ -233,13 +233,13 @@ private extension ActivityManager { return value?.components(separatedBy: " ").filter { $0.count > 2 } ?? [] } - func updateSelectingActivityFeedSearchAttributes(with feed: Feed) { + func updateSelectingActivityFeedSearchAttributes(with feed: WebFeed) { let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String) attributeSet.title = feed.nameForDisplay attributeSet.keywords = makeKeywords(feed.nameForDisplay) attributeSet.relatedUniqueIdentifier = ActivityManager.identifer(for: feed) - if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) { + if let iconImage = appDelegate.webFeedIconDownloader.icon(for: feed) { attributeSet.thumbnailData = iconImage.image.dataRepresentation() } else if let iconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) { attributeSet.thumbnailData = iconImage.image.dataRepresentation() @@ -267,15 +267,15 @@ private extension ActivityManager { return "account_\(folder.account!.accountID)_folder_\(folder.nameForDisplay)" } - static func identifer(for feed: Feed) -> String { - return "account_\(feed.account!.accountID)_feed_\(feed.feedID)" + static func identifer(for feed: WebFeed) -> String { + return "account_\(feed.account!.accountID)_feed_\(feed.webFeedID)" } static func identifer(for article: Article) -> String { - return "account_\(article.accountID)_feed_\(article.feedID)_article_\(article.articleID)" + return "account_\(article.accountID)_feed_\(article.webFeedID)_article_\(article.articleID)" } - static func identifers(for feed: Feed) -> [String] { + static func identifers(for feed: WebFeed) -> [String] { var ids = [String]() ids.append(identifer(for: feed)) diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index ae74fd84c..f7572be05 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -144,9 +144,9 @@ private extension ArticleRenderer { d["avatars"] = ""; var feedLink = "" - if let feedTitle = article.feed?.nameForDisplay { + if let feedTitle = article.webFeed?.nameForDisplay { feedLink = feedTitle - if let feedURL = article.feed?.homePageURL { + if let feedURL = article.webFeed?.homePageURL { feedLink = feedLink.htmlByAddingLink(feedURL, className: "feedLink") } } @@ -184,7 +184,7 @@ private extension ArticleRenderer { } func byline() -> String { - guard let authors = article?.authors ?? article?.feed?.authors, !authors.isEmpty else { + guard let authors = article?.authors ?? article?.webFeed?.authors, !authors.isEmpty else { return "" } @@ -192,7 +192,7 @@ private extension ArticleRenderer { // This code assumes that multiple authors would never match the feed name so that // if there feed owner has an article co-author all authors are given the byline. if authors.count == 1, let author = authors.first { - if author.name == article?.feed?.nameForDisplay { + if author.name == article?.webFeed?.nameForDisplay { return "" } } @@ -256,10 +256,10 @@ private extension Article { var baseURL: URL? { var s = url if s == nil { - s = feed?.homePageURL + s = webFeed?.homePageURL } if s == nil { - s = feed?.url + s = webFeed?.url } guard let urlString = s else { diff --git a/Shared/Commands/DeleteCommand.swift b/Shared/Commands/DeleteCommand.swift index 1b37ff7aa..4567c7fa2 100644 --- a/Shared/Commands/DeleteCommand.swift +++ b/Shared/Commands/DeleteCommand.swift @@ -63,7 +63,7 @@ final class DeleteCommand: UndoableCommand { } for node in nodes { - if let _ = node.representedObject as? Feed { + if let _ = node.representedObject as? WebFeed { continue } if let _ = node.representedObject as? Folder { @@ -84,7 +84,7 @@ private struct SidebarItemSpecifier { private weak var account: Account? private let parentFolder: Folder? private let folder: Folder? - private let feed: Feed? + private let webFeed: WebFeed? private let path: ContainerPath private let errorHandler: (Error) -> () @@ -104,13 +104,13 @@ private struct SidebarItemSpecifier { self.parentFolder = node.parentFolder() - if let feed = node.representedObject as? Feed { - self.feed = feed + if let webFeed = node.representedObject as? WebFeed { + self.webFeed = webFeed self.folder = nil - account = feed.account + account = webFeed.account } else if let folder = node.representedObject as? Folder { - self.feed = nil + self.webFeed = nil self.folder = folder account = folder.account } @@ -130,7 +130,7 @@ private struct SidebarItemSpecifier { func delete(completion: @escaping () -> Void) { - if let feed = feed { + if let webFeed = webFeed { guard let container = path.resolveContainer() else { completion() @@ -138,7 +138,7 @@ private struct SidebarItemSpecifier { } BatchUpdate.shared.start() - account?.removeFeed(feed, from: container) { result in + account?.removeWebFeed(webFeed, from: container) { result in BatchUpdate.shared.end() completion() self.checkResult(result) @@ -158,22 +158,22 @@ private struct SidebarItemSpecifier { func restore() { - if let _ = feed { - restoreFeed() + if let _ = webFeed { + restoreWebFeed() } else if let _ = folder { restoreFolder() } } - private func restoreFeed() { + private func restoreWebFeed() { - guard let account = account, let feed = feed, let container = path.resolveContainer() else { + guard let account = account, let feed = webFeed, let container = path.resolveContainer() else { return } BatchUpdate.shared.start() - account.restoreFeed(feed, container: container) { result in + account.restoreWebFeed(feed, container: container) { result in BatchUpdate.shared.end() self.checkResult(result) } @@ -257,7 +257,7 @@ private struct DeleteActionName { var numberOfFolders = 0 for node in nodes { - if let _ = node.representedObject as? Feed { + if let _ = node.representedObject as? WebFeed { numberOfFeeds += 1 } else if let _ = node.representedObject as? Folder { diff --git a/Shared/Commands/SendToMarsEditCommand.swift b/Shared/Commands/SendToMarsEditCommand.swift index 3a9fd33b2..bcf277ac6 100644 --- a/Shared/Commands/SendToMarsEditCommand.swift +++ b/Shared/Commands/SendToMarsEditCommand.swift @@ -57,7 +57,7 @@ private extension SendToMarsEditCommand { let body = article.contentHTML ?? article.contentText ?? article.summary let authorName = article.authors?.first?.name - let sender = SendToBlogEditorApp(targetDesciptor: targetDescriptor, title: article.title, body: body, summary: article.summary, link: article.externalURL, permalink: article.url, subject: nil, creator: authorName, commentsURL: nil, guid: article.uniqueID, sourceName: article.feed?.nameForDisplay, sourceHomeURL: article.feed?.homePageURL, sourceFeedURL: article.feed?.url) + let sender = SendToBlogEditorApp(targetDesciptor: targetDescriptor, title: article.title, body: body, summary: article.summary, link: article.externalURL, permalink: article.url, subject: nil, creator: authorName, commentsURL: nil, guid: article.uniqueID, sourceName: article.webFeed?.nameForDisplay, sourceHomeURL: article.webFeed?.homePageURL, sourceFeedURL: article.webFeed?.url) let _ = sender.send() } diff --git a/Shared/Commands/SendToMicroBlogCommand.swift b/Shared/Commands/SendToMicroBlogCommand.swift index 59fc34987..58a3a2d6b 100644 --- a/Shared/Commands/SendToMicroBlogCommand.swift +++ b/Shared/Commands/SendToMicroBlogCommand.swift @@ -68,10 +68,10 @@ private extension Article { // Feed name, or feed name + author name (if author is specified per-article). // Includes trailing space. - if let feedName = feed?.nameForDisplay, let authorName = authors?.first?.name { + if let feedName = webFeed?.nameForDisplay, let authorName = authors?.first?.name { return feedName + ", " + authorName + ": " } - if let feedName = feed?.nameForDisplay { + if let feedName = webFeed?.nameForDisplay { return feedName + ": " } return "" diff --git a/Shared/Data/ArticleUtilities.swift b/Shared/Data/ArticleUtilities.swift index 72a5afe7e..ffdfdb250 100644 --- a/Shared/Data/ArticleUtilities.swift +++ b/Shared/Data/ArticleUtilities.swift @@ -42,8 +42,8 @@ private func accountAndArticlesDictionary(_ articles: Set
) -> [String: extension Article { - var feed: Feed? { - return account?.existingFeed(withFeedID: feedID) + var webFeed: WebFeed? { + return account?.existingWebFeed(withWebFeedID: webFeedID) } var preferredLink: String? { @@ -71,26 +71,26 @@ extension Article { } } - if let authors = feed?.authors, authors.count == 1, let author = authors.first { + if let authors = webFeed?.authors, authors.count == 1, let author = authors.first { if let image = appDelegate.authorAvatarDownloader.image(for: author) { return image } } - guard let feed = feed else { + guard let webFeed = webFeed else { return nil } - let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed) + let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed) if feedIconImage != nil { return feedIconImage } - if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) { + if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) { return faviconImage } - return FaviconGenerator.favicon(feed) + return FaviconGenerator.favicon(webFeed) } } @@ -99,7 +99,7 @@ extension Article { struct ArticlePathKey { static let accountID = "accountID" static let accountName = "accountName" - static let feedID = "feedID" + static let webFeedID = "webFeedID" static let articleID = "articleID" } @@ -109,7 +109,7 @@ extension Article { return [ ArticlePathKey.accountID: accountID, ArticlePathKey.accountName: account?.nameForDisplay ?? "", - ArticlePathKey.feedID: feedID, + ArticlePathKey.webFeedID: webFeedID, ArticlePathKey.articleID: articleID ] } @@ -121,7 +121,7 @@ extension Article { extension Article: SortableArticle { var sortableName: String { - return feed?.name ?? "" + return webFeed?.name ?? "" } var sortableDate: Date { @@ -132,8 +132,8 @@ extension Article: SortableArticle { return articleID } - var sortableFeedID: String { - return feedID + var sortableWebFeedID: String { + return webFeedID } } diff --git a/Shared/Data/SmallIconProvider.swift b/Shared/Data/SmallIconProvider.swift index a070dec30..3fb85ec9d 100644 --- a/Shared/Data/SmallIconProvider.swift +++ b/Shared/Data/SmallIconProvider.swift @@ -16,7 +16,7 @@ protocol SmallIconProvider { var smallIcon: IconImage? { get } } -extension Feed: SmallIconProvider { +extension WebFeed: SmallIconProvider { var smallIcon: IconImage? { if let iconImage = appDelegate.faviconDownloader.favicon(for: self) { diff --git a/Shared/Favicons/FaviconDownloader.swift b/Shared/Favicons/FaviconDownloader.swift index 610caf94b..e18be583e 100644 --- a/Shared/Favicons/FaviconDownloader.swift +++ b/Shared/Favicons/FaviconDownloader.swift @@ -41,7 +41,7 @@ final class FaviconDownloader { } private let queue: DispatchQueue - private var cache = [Feed: IconImage]() // faviconURL: RSImage + private var cache = [WebFeed: IconImage]() // faviconURL: RSImage struct UserInfoKey { static let faviconURL = "faviconURL" @@ -64,21 +64,21 @@ final class FaviconDownloader { // MARK: - API func resetCache() { - cache = [Feed: IconImage]() + cache = [WebFeed: IconImage]() } - func favicon(for feed: Feed) -> IconImage? { + func favicon(for webFeed: WebFeed) -> IconImage? { assert(Thread.isMainThread) - if let faviconURL = feed.faviconURL { + if let faviconURL = webFeed.faviconURL { return favicon(with: faviconURL) } - var homePageURL = feed.homePageURL + var homePageURL = webFeed.homePageURL if homePageURL == nil { // Base homePageURL off feedURL if needed. Won’t always be accurate, but is good enough. - if let feedURL = URL(string: feed.url), let scheme = feedURL.scheme, let host = feedURL.host { + if let feedURL = URL(string: webFeed.url), let scheme = feedURL.scheme, let host = feedURL.host { homePageURL = scheme + "://" + host + "/" } } @@ -89,16 +89,16 @@ final class FaviconDownloader { return nil } - func faviconAsIcon(for feed: Feed) -> IconImage? { + func faviconAsIcon(for webFeed: WebFeed) -> IconImage? { - if let image = cache[feed] { + if let image = cache[webFeed] { return image } - if let iconImage = favicon(for: feed), let imageData = iconImage.image.dataRepresentation() { + if let iconImage = favicon(for: webFeed), let imageData = iconImage.image.dataRepresentation() { if let scaledImage = RSImage.scaledForIcon(imageData) { let scaledIconImage = IconImage(scaledImage) - cache[feed] = scaledIconImage + cache[webFeed] = scaledIconImage return scaledIconImage } } diff --git a/Shared/Favicons/FaviconGenerator.swift b/Shared/Favicons/FaviconGenerator.swift index 787bf56db..3a19ac7ef 100644 --- a/Shared/Favicons/FaviconGenerator.swift +++ b/Shared/Favicons/FaviconGenerator.swift @@ -14,16 +14,16 @@ final class FaviconGenerator { private static var faviconGeneratorCache = [String: IconImage]() // feedURL: RSImage - static func favicon(_ feed: Feed) -> IconImage { + static func favicon(_ webFeed: WebFeed) -> IconImage { - if let favicon = FaviconGenerator.faviconGeneratorCache[feed.url] { + if let favicon = FaviconGenerator.faviconGeneratorCache[webFeed.url] { return favicon } - let colorHash = ColorHash(feed.url) + let colorHash = ColorHash(webFeed.url) if let favicon = AppAssets.faviconTemplateImage.maskWithColor(color: colorHash.color.cgColor) { let iconImage = IconImage(favicon) - FaviconGenerator.faviconGeneratorCache[feed.url] = iconImage + FaviconGenerator.faviconGeneratorCache[webFeed.url] = iconImage return iconImage } else { return IconImage(AppAssets.faviconTemplateImage) diff --git a/Shared/Images/FeedIconDownloader.swift b/Shared/Images/WebFeedIconDownloader.swift similarity index 84% rename from Shared/Images/FeedIconDownloader.swift rename to Shared/Images/WebFeedIconDownloader.swift index 7fb4c042b..f102ef212 100644 --- a/Shared/Images/FeedIconDownloader.swift +++ b/Shared/Images/WebFeedIconDownloader.swift @@ -15,10 +15,10 @@ import RSParser extension Notification.Name { - static let FeedIconDidBecomeAvailable = Notification.Name("FeedIconDidBecomeAvailableNotification") // UserInfoKey.feed + static let WebFeedIconDidBecomeAvailable = Notification.Name("WebFeedIconDidBecomeAvailableNotification") // UserInfoKey.feed } -public final class FeedIconDownloader { +public final class WebFeedIconDownloader { private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0) @@ -45,8 +45,8 @@ public final class FeedIconDownloader { }() private var urlsInProgress = Set() - private var cache = [Feed: IconImage]() - private var waitingForFeedURLs = [String: Feed]() + private var cache = [WebFeed: IconImage]() + private var waitingForFeedURLs = [String: WebFeed]() init(imageDownloader: ImageDownloader, folder: String) { self.imageDownloader = imageDownloader @@ -58,10 +58,10 @@ public final class FeedIconDownloader { } func resetCache() { - cache = [Feed: IconImage]() + cache = [WebFeed: IconImage]() } - func icon(for feed: Feed) -> IconImage? { + func icon(for feed: WebFeed) -> IconImage? { if let cachedImage = cache[feed] { return cachedImage @@ -120,9 +120,9 @@ public final class FeedIconDownloader { } -private extension FeedIconDownloader { +private extension WebFeedIconDownloader { - func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) { + func icon(forHomePageURL homePageURL: String, feed: WebFeed, _ imageResultBlock: @escaping (RSImage?) -> Void) { if homePagesWithNoIconURLCache.contains(homePageURL) || homePagesWithUglyIcons.contains(homePageURL) { imageResultBlock(nil) @@ -137,7 +137,7 @@ private extension FeedIconDownloader { findIconURLForHomePageURL(homePageURL, feed: feed) } - func icon(forURL url: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) { + func icon(forURL url: String, feed: WebFeed, _ imageResultBlock: @escaping (RSImage?) -> Void) { waitingForFeedURLs[url] = feed guard let imageData = imageDownloader.image(for: url) else { imageResultBlock(nil) @@ -146,11 +146,11 @@ private extension FeedIconDownloader { RSImage.scaledForIcon(imageData, imageResultBlock: imageResultBlock) } - func postFeedIconDidBecomeAvailableNotification(_ feed: Feed) { + func postFeedIconDidBecomeAvailableNotification(_ feed: WebFeed) { DispatchQueue.main.async { - let userInfo: [AnyHashable: Any] = [UserInfoKey.feed: feed] - NotificationCenter.default.post(name: .FeedIconDidBecomeAvailable, object: self, userInfo: userInfo) + let userInfo: [AnyHashable: Any] = [UserInfoKey.webFeed: feed] + NotificationCenter.default.post(name: .WebFeedIconDidBecomeAvailable, object: self, userInfo: userInfo) } } @@ -166,7 +166,7 @@ private extension FeedIconDownloader { homePageToIconURLCacheDirty = true } - func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed) { + func findIconURLForHomePageURL(_ homePageURL: String, feed: WebFeed) { guard !urlsInProgress.contains(homePageURL) else { return @@ -183,7 +183,7 @@ private extension FeedIconDownloader { } } - func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String, feed: Feed) { + func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String, feed: WebFeed) { if let url = metadata.bestWebsiteIconURL() { cacheIconURL(for: homePageURL, url) @@ -216,11 +216,11 @@ private extension FeedIconDownloader { } func queueSaveHomePageToIconURLCacheIfNeeded() { - FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded)) + WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded)) } func queueHomePagesWithNoIconURLCacheIfNeeded() { - FeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded)) + WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded)) } func saveHomePageToIconURLCache() { diff --git a/Shared/Timeline/ArticleSorter.swift b/Shared/Timeline/ArticleSorter.swift index c7ba1a748..50e91d8a8 100644 --- a/Shared/Timeline/ArticleSorter.swift +++ b/Shared/Timeline/ArticleSorter.swift @@ -13,7 +13,7 @@ protocol SortableArticle { var sortableName: String { get } var sortableDate: Date { get } var sortableArticleID: String { get } - var sortableFeedID: String { get } + var sortableWebFeedID: String { get } } struct ArticleSorter { @@ -34,7 +34,7 @@ struct ArticleSorter { sortByDateDirection: ComparisonResult) -> [T] { // Group articles by "feed-feedID" - feed ID is used to differentiate between // two feeds that have the same name - let groupedArticles = Dictionary(grouping: articles) { "\($0.sortableName.lowercased())-\($0.sortableFeedID)" } + let groupedArticles = Dictionary(grouping: articles) { "\($0.sortableName.lowercased())-\($0.sortableWebFeedID)" } return groupedArticles .sorted { $0.key < $1.key } .flatMap { (tuple) -> [T] in diff --git a/Shared/Tree/FeedTreeControllerDelegate.swift b/Shared/Tree/WebFeedTreeControllerDelegate.swift similarity index 88% rename from Shared/Tree/FeedTreeControllerDelegate.swift rename to Shared/Tree/WebFeedTreeControllerDelegate.swift index 936efd4ea..62409cd14 100644 --- a/Shared/Tree/FeedTreeControllerDelegate.swift +++ b/Shared/Tree/WebFeedTreeControllerDelegate.swift @@ -11,7 +11,7 @@ import RSTree import Articles import Account -final class FeedTreeControllerDelegate: TreeControllerDelegate { +final class WebFeedTreeControllerDelegate: TreeControllerDelegate { func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? { @@ -29,7 +29,7 @@ final class FeedTreeControllerDelegate: TreeControllerDelegate { } } -private extension FeedTreeControllerDelegate { +private extension WebFeedTreeControllerDelegate { func childNodesForRootNode(_ rootNode: Node) -> [Node]? { @@ -52,7 +52,7 @@ private extension FeedTreeControllerDelegate { let container = containerNode.representedObject as! Container var children = [AnyObject]() - children.append(contentsOf: Array(container.topLevelFeeds)) + children.append(contentsOf: Array(container.topLevelWebFeeds)) if let folders = container.folders { children.append(contentsOf: Array(folders)) } @@ -78,8 +78,8 @@ private extension FeedTreeControllerDelegate { func createNode(representedObject: Any, parent: Node) -> Node? { - if let feed = representedObject as? Feed { - return createNode(feed: feed, parent: parent) + if let webFeed = representedObject as? WebFeed { + return createNode(webFeed: webFeed, parent: parent) } if let folder = representedObject as? Folder { return createNode(folder: folder, parent: parent) @@ -91,9 +91,9 @@ private extension FeedTreeControllerDelegate { return nil } - func createNode(feed: Feed, parent: Node) -> Node { + func createNode(webFeed: WebFeed, parent: Node) -> Node { - return parent.createChildNode(feed) + return parent.createChildNode(webFeed) } func createNode(folder: Folder, parent: Node) -> Node { diff --git a/Shared/UserInfoKey.swift b/Shared/UserInfoKey.swift index dfdce4e87..1318b0ea0 100644 --- a/Shared/UserInfoKey.swift +++ b/Shared/UserInfoKey.swift @@ -17,7 +17,7 @@ struct UserInfoKey { static let articles = "articles" static let navigationKeyPressed = "navigationKeyPressed" static let objects = "objects" - static let feed = "feed" + static let webFeed = "webFeed" static let url = "url" static let author = "author" static let articlePath = "articlePath" diff --git a/Shared/UserNotifications/UserNotificationManager.swift b/Shared/UserNotifications/UserNotificationManager.swift index 2b42228ec..04505b66f 100644 --- a/Shared/UserNotifications/UserNotificationManager.swift +++ b/Shared/UserNotifications/UserNotificationManager.swift @@ -25,8 +25,8 @@ final class UserNotificationManager: NSObject { } for article in articles { - if !article.status.read, let feed = article.feed, feed.isNotifyAboutNewArticles ?? false { - sendNotification(feed: feed, article: article) + if !article.status.read, let webFeed = article.webFeed, webFeed.isNotifyAboutNewArticles ?? false { + sendNotification(webFeed: webFeed, article: article) } } } @@ -43,10 +43,10 @@ final class UserNotificationManager: NSObject { private extension UserNotificationManager { - private func sendNotification(feed: Feed, article: Article) { + private func sendNotification(webFeed: WebFeed, article: Article) { let content = UNMutableNotificationContent() - content.title = feed.nameForDisplay + content.title = webFeed.nameForDisplay content.body = ArticleStringFormatter.truncatedTitle(article) if content.body.isEmpty { content.body = ArticleStringFormatter.truncatedSummary(article) diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index 00ee8aa74..9e37ccd87 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -10,7 +10,7 @@ - + @@ -78,7 +78,7 @@ - + - + diff --git a/iOS/Inspector/FeedInspectorViewController.swift b/iOS/Inspector/WebFeedInspectorViewController.swift similarity index 61% rename from iOS/Inspector/FeedInspectorViewController.swift rename to iOS/Inspector/WebFeedInspectorViewController.swift index d52a8275f..ff790e84f 100644 --- a/iOS/Inspector/FeedInspectorViewController.swift +++ b/iOS/Inspector/WebFeedInspectorViewController.swift @@ -1,5 +1,5 @@ // -// FeedInspectorViewController.swift +// WebFeedInspectorViewController.swift // NetNewsWire-iOS // // Created by Maurice Parker on 11/6/19. @@ -9,11 +9,11 @@ import UIKit import Account -class FeedInspectorViewController: UITableViewController { +class WebFeedInspectorViewController: UITableViewController { static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 500.0) - var feed: Feed! + var webFeed: WebFeed! @IBOutlet weak var nameTextField: UITextField! @IBOutlet weak var notifyAboutNewArticlesSwitch: UISwitch! @IBOutlet weak var alwaysShowReaderViewSwitch: UISwitch! @@ -22,49 +22,49 @@ class FeedInspectorViewController: UITableViewController { private var headerView: InspectorIconHeaderView? private var iconImage: IconImage { - if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) { + if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: webFeed) { return feedIcon } - if let favicon = appDelegate.faviconDownloader.favicon(for: feed) { + if let favicon = appDelegate.faviconDownloader.favicon(for: webFeed) { return favicon } - return FaviconGenerator.favicon(feed) + return FaviconGenerator.favicon(webFeed) } override func viewDidLoad() { tableView.register(InspectorIconHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") - navigationItem.title = feed.nameForDisplay - nameTextField.text = feed.nameForDisplay + navigationItem.title = webFeed.nameForDisplay + nameTextField.text = webFeed.nameForDisplay - notifyAboutNewArticlesSwitch.setOn(feed.isNotifyAboutNewArticles ?? false, animated: false) - alwaysShowReaderViewSwitch.setOn(feed.isArticleExtractorAlwaysOn ?? false, animated: false) + notifyAboutNewArticlesSwitch.setOn(webFeed.isNotifyAboutNewArticles ?? false, animated: false) + alwaysShowReaderViewSwitch.setOn(webFeed.isArticleExtractorAlwaysOn ?? false, animated: false) - homePageLabel.text = feed.homePageURL - feedURLLabel.text = feed.url + homePageLabel.text = webFeed.homePageURL + feedURLLabel.text = webFeed.url - NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) } override func viewDidDisappear(_ animated: Bool) { - if nameTextField.text != feed.nameForDisplay { + if nameTextField.text != webFeed.nameForDisplay { let nameText = nameTextField.text ?? "" - let newName = nameText.isEmpty ? (feed.name ?? NSLocalizedString("Untitled", comment: "Feed name")) : nameText - feed.rename(to: newName) { _ in } + let newName = nameText.isEmpty ? (webFeed.name ?? NSLocalizedString("Untitled", comment: "Feed name")) : nameText + webFeed.rename(to: newName) { _ in } } } // MARK: Notifications - @objc func feedIconDidBecomeAvailable(_ notification: Notification) { + @objc func webFeedIconDidBecomeAvailable(_ notification: Notification) { headerView?.iconView.iconImage = iconImage } @IBAction func notifyAboutNewArticlesChanged(_ sender: Any) { - feed.isNotifyAboutNewArticles = notifyAboutNewArticlesSwitch.isOn + webFeed.isNotifyAboutNewArticles = notifyAboutNewArticlesSwitch.isOn } @IBAction func alwaysShowReaderViewChanged(_ sender: Any) { - feed.isArticleExtractorAlwaysOn = alwaysShowReaderViewSwitch.isOn + webFeed.isArticleExtractorAlwaysOn = alwaysShowReaderViewSwitch.isOn } @IBAction func done(_ sender: Any) { @@ -75,7 +75,7 @@ class FeedInspectorViewController: UITableViewController { // MARK: Table View -extension FeedInspectorViewController { +extension WebFeedInspectorViewController { override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return section == 0 ? 64.0 : super.tableView(tableView, heightForHeaderInSection: section) @@ -95,7 +95,7 @@ extension FeedInspectorViewController { // MARK: UITextFieldDelegate -extension FeedInspectorViewController: UITextFieldDelegate { +extension WebFeedInspectorViewController: UITextFieldDelegate { func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() diff --git a/iOS/Intents/AddFeedIntentHandler.swift b/iOS/Intents/AddWebFeedIntentHandler.swift similarity index 61% rename from iOS/Intents/AddFeedIntentHandler.swift rename to iOS/Intents/AddWebFeedIntentHandler.swift index ab822fd16..196e6735c 100644 --- a/iOS/Intents/AddFeedIntentHandler.swift +++ b/iOS/Intents/AddWebFeedIntentHandler.swift @@ -1,5 +1,5 @@ // -// AddFeedIntentHandler.swift +// AddWebFeedIntentHandler.swift // NetNewsWire // // Created by Maurice Parker on 10/18/19. @@ -9,7 +9,7 @@ import Intents import Account -public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling { +public class AddWebFeedIntentHandler: NSObject, AddWebFeedIntentHandling { override init() { super.init() @@ -18,7 +18,7 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling { } } - public func resolveUrl(for intent: AddFeedIntent, with completion: @escaping (AddFeedUrlResolutionResult) -> Void) { + public func resolveUrl(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedUrlResolutionResult) -> Void) { guard let url = intent.url else { completion(.unsupported(forReason: .required)) return @@ -26,16 +26,16 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling { completion(.success(with: url)) } - public func provideAccountNameOptions(for intent: AddFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) { + public func provideAccountNameOptions(for intent: AddWebFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) { DispatchQueue.main.async { let accountNames = AccountManager.shared.activeAccounts.compactMap { $0.nameForDisplay } completion(accountNames, nil) } } - public func resolveAccountName(for intent: AddFeedIntent, with completion: @escaping (AddFeedAccountNameResolutionResult) -> Void) { + public func resolveAccountName(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedAccountNameResolutionResult) -> Void) { guard let accountName = intent.accountName else { - completion(AddFeedAccountNameResolutionResult.notRequired()) + completion(AddWebFeedAccountNameResolutionResult.notRequired()) return } DispatchQueue.main.async { @@ -47,7 +47,7 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling { } } - public func provideFolderNameOptions(for intent: AddFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) { + public func provideFolderNameOptions(for intent: AddWebFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) { DispatchQueue.main.async { guard let accountName = intent.accountName, let account = AccountManager.shared.findActiveAccount(forDisplayName: accountName) else { completion([String](), nil) @@ -59,9 +59,9 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling { } } - public func resolveFolderName(for intent: AddFeedIntent, with completion: @escaping (AddFeedFolderNameResolutionResult) -> Void) { + public func resolveFolderName(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedFolderNameResolutionResult) -> Void) { guard let accountName = intent.accountName, let folderName = intent.folderName else { - completion(AddFeedFolderNameResolutionResult.notRequired()) + completion(AddWebFeedFolderNameResolutionResult.notRequired()) return } @@ -79,9 +79,9 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling { } } - public func handle(intent: AddFeedIntent, completion: @escaping (AddFeedIntentResponse) -> Void) { + public func handle(intent: AddWebFeedIntent, completion: @escaping (AddWebFeedIntentResponse) -> Void) { guard let url = intent.url else { - completion(AddFeedIntentResponse(code: .failure, userActivity: nil)) + completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil)) return } @@ -96,7 +96,7 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling { }() guard let validAccount = account else { - completion(AddFeedIntentResponse(code: .failure, userActivity: nil)) + completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil)) return } @@ -109,22 +109,22 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling { }() guard let validContainer = container else { - completion(AddFeedIntentResponse(code: .failure, userActivity: nil)) + completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil)) return } - validAccount.createFeed(url: url.absoluteString, name: nil, container: validContainer) { result in + validAccount.createWebFeed(url: url.absoluteString, name: nil, container: validContainer) { result in switch result { case .success: - completion(AddFeedIntentResponse(code: .success, userActivity: nil)) + completion(AddWebFeedIntentResponse(code: .success, userActivity: nil)) case .failure(let error): switch error { case AccountError.createErrorNotFound: - completion(AddFeedIntentResponse(code: .feedNotFound, userActivity: nil)) + completion(AddWebFeedIntentResponse(code: .feedNotFound, userActivity: nil)) case AccountError.createErrorAlreadySubscribed: - completion(AddFeedIntentResponse(code: .alreadySubscribed, userActivity: nil)) + completion(AddWebFeedIntentResponse(code: .alreadySubscribed, userActivity: nil)) default: - completion(AddFeedIntentResponse(code: .failure, userActivity: nil)) + completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil)) } } } diff --git a/iOS/Intents/Base.lproj/Intents.intentdefinition b/iOS/Intents/Base.lproj/Intents.intentdefinition index 7994aa105..65b3af477 100644 --- a/iOS/Intents/Base.lproj/Intents.intentdefinition +++ b/iOS/Intents/Base.lproj/Intents.intentdefinition @@ -9,11 +9,11 @@ INIntentDefinitionNamespace U6u7RF INIntentDefinitionSystemVersion - 19A602 + 19B88 INIntentDefinitionToolsBuildVersion - 11B41 + 11B53 INIntentDefinitionToolsVersion - 11.2 + 11.2.1 INIntents @@ -22,7 +22,7 @@ INIntentConfigurable INIntentDescription - Add a feed + Add a web feed INIntentDescriptionID IuAbef INIntentIneligibleForSuggestions @@ -59,7 +59,7 @@ INIntentName - AddFeed + AddWebFeed INIntentParameters @@ -303,7 +303,7 @@ INIntentTitle - Add Feed + Add Web Feed INIntentTitleID oV681v INIntentType diff --git a/iOS/IntentsExtension/Info.plist b/iOS/IntentsExtension/Info.plist index 8c87f51c5..5e81a246a 100644 --- a/iOS/IntentsExtension/Info.plist +++ b/iOS/IntentsExtension/Info.plist @@ -34,7 +34,7 @@ IntentsSupported - AddFeedIntent + AddWebFeedIntent NSExtensionPointIdentifier diff --git a/iOS/IntentsExtension/IntentHandler.swift b/iOS/IntentsExtension/IntentHandler.swift index e3e1490a8..e1c80701f 100644 --- a/iOS/IntentsExtension/IntentHandler.swift +++ b/iOS/IntentsExtension/IntentHandler.swift @@ -12,8 +12,8 @@ class IntentHandler: INExtension { override func handler(for intent: INIntent) -> Any { switch intent { - case is AddFeedIntent: - return AddFeedIntentHandler() + case is AddWebFeedIntent: + return AddWebFeedIntentHandler() default: fatalError("Unhandled intent type: \(intent)") } diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift index 2644d4404..5dfa1a492 100644 --- a/iOS/KeyboardManager.swift +++ b/iOS/KeyboardManager.swift @@ -117,7 +117,7 @@ private extension KeyboardManager { static func globalAuxilaryKeyCommands() -> [UIKeyCommand] { var keys = [UIKeyCommand]() - let addNewFeedTitle = NSLocalizedString("New Feed", comment: "New Feed") + let addNewFeedTitle = NSLocalizedString("New Web Feed", comment: "New Web Feed") keys.append(KeyboardManager.createKeyCommand(title: addNewFeedTitle, action: "addNewFeed:", input: "n", modifiers: [.command])) let addNewFolderTitle = NSLocalizedString("New Folder", comment: "New Folder") diff --git a/iOS/MasterFeed/MasterFeedDataSource.swift b/iOS/MasterFeed/MasterFeedDataSource.swift index e7f363604..b2353fe0f 100644 --- a/iOS/MasterFeed/MasterFeedDataSource.swift +++ b/iOS/MasterFeed/MasterFeedDataSource.swift @@ -34,12 +34,12 @@ class MasterFeedDataSource: UITableViewDiffableDataSource { guard let node = itemIdentifier(for: indexPath) else { return false } - return node.representedObject is Feed + return node.representedObject is WebFeed } override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { - guard let sourceNode = itemIdentifier(for: sourceIndexPath), let feed = sourceNode.representedObject as? Feed else { + guard let sourceNode = itemIdentifier(for: sourceIndexPath), let webFeed = sourceNode.representedObject as? WebFeed else { return } @@ -67,15 +67,15 @@ class MasterFeedDataSource: UITableViewDiffableDataSource { } }() - // Move the Feed + // Move the Web Feed guard let source = sourceNode.parent?.representedObject as? Container, let destination = destParentNode?.representedObject as? Container else { return } if sameAccount(sourceNode, destParentNode!) { - moveFeedInAccount(feed: feed, sourceContainer: source, destinationContainer: destination) + moveWebFeedInAccount(feed: webFeed, sourceContainer: source, destinationContainer: destination) } else { - moveFeedBetweenAccounts(feed: feed, sourceContainer: source, destinationContainer: destination) + moveWebFeedBetweenAccounts(feed: webFeed, sourceContainer: source, destinationContainer: destination) } } @@ -94,8 +94,8 @@ class MasterFeedDataSource: UITableViewDiffableDataSource { return account } else if let folder = node.representedObject as? Folder { return folder.account - } else if let feed = node.representedObject as? Feed { - return feed.account + } else if let webFeed = node.representedObject as? WebFeed { + return webFeed.account } else { return nil } @@ -106,9 +106,9 @@ class MasterFeedDataSource: UITableViewDiffableDataSource { return nodeAccount(node)?.accountID } - func moveFeedInAccount(feed: Feed, sourceContainer: Container, destinationContainer: Container) { + func moveWebFeedInAccount(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) { BatchUpdate.shared.start() - sourceContainer.account?.moveFeed(feed, from: sourceContainer, to: destinationContainer) { result in + sourceContainer.account?.moveWebFeed(feed, from: sourceContainer, to: destinationContainer) { result in BatchUpdate.shared.end() switch result { case .success: @@ -119,15 +119,15 @@ class MasterFeedDataSource: UITableViewDiffableDataSource { } } - func moveFeedBetweenAccounts(feed: Feed, sourceContainer: Container, destinationContainer: Container) { + func moveWebFeedBetweenAccounts(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) { - if let existingFeed = destinationContainer.account?.existingFeed(withURL: feed.url) { + if let existingFeed = destinationContainer.account?.existingWebFeed(withURL: feed.url) { BatchUpdate.shared.start() - destinationContainer.account?.addFeed(existingFeed, to: destinationContainer) { result in + destinationContainer.account?.addWebFeed(existingFeed, to: destinationContainer) { result in switch result { case .success: - sourceContainer.account?.removeFeed(feed, from: sourceContainer) { result in + sourceContainer.account?.removeWebFeed(feed, from: sourceContainer) { result in BatchUpdate.shared.end() switch result { case .success: @@ -145,10 +145,10 @@ class MasterFeedDataSource: UITableViewDiffableDataSource { } else { BatchUpdate.shared.start() - destinationContainer.account?.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in + destinationContainer.account?.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in switch result { case .success: - sourceContainer.account?.removeFeed(feed, from: sourceContainer) { result in + sourceContainer.account?.removeWebFeed(feed, from: sourceContainer) { result in BatchUpdate.shared.end() switch result { case .success: diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index df76e98d4..b0acd64ba 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -53,9 +53,9 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(feedMetadataDidChange(_:)), name: .FeedMetadataDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(webFeedMetadataDidChange(_:)), name: .WebFeedMetadataDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) @@ -117,31 +117,31 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { applyToAvailableCells(configureIcon) } - @objc func feedIconDidBecomeAvailable(_ note: Notification) { - guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { + @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { + guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else { return } - applyToCellsForRepresentedObject(feed, configureIcon(_:_:)) + applyToCellsForRepresentedObject(webFeed, configureIcon(_:_:)) } - @objc func feedSettingDidChange(_ note: Notification) { - guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else { + @objc func webFeedSettingDidChange(_ note: Notification) { + guard let webFeed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else { return } - if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL { - configureCellsForRepresentedObject(feed) + if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL { + configureCellsForRepresentedObject(webFeed) } } - @objc func feedMetadataDidChange(_ note: Notification) { + @objc func webFeedMetadataDidChange(_ note: Notification) { reloadAllVisibleCells() } @objc func userDidAddFeed(_ notification: Notification) { - guard let feed = notification.userInfo?[UserInfoKey.feed] as? Feed else { + guard let webFeed = notification.userInfo?[UserInfoKey.webFeed] as? WebFeed else { return } - discloseFeed(feed, animated: true) + discloseFeed(webFeed, animated: true) } @objc func contentSizeCategoryDidChange(_ note: Notification) { @@ -237,13 +237,13 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { renameAction.backgroundColor = UIColor.systemOrange actions.append(renameAction) - if let feed = dataSource.itemIdentifier(for: indexPath)?.representedObject as? Feed { + if let webFeed = dataSource.itemIdentifier(for: indexPath)?.representedObject as? WebFeed { let moreTitle = NSLocalizedString("More", comment: "More") let moreAction = UIContextualAction(style: .normal, title: moreTitle) { [weak self] (action, view, completionHandler) in if let self = self { - let alert = UIAlertController(title: feed.nameForDisplay, message: nil, preferredStyle: .actionSheet) + let alert = UIAlertController(title: webFeed.nameForDisplay, message: nil, preferredStyle: .actionSheet) if let popoverController = alert.popoverPresentationController { popoverController.sourceView = view popoverController.sourceRect = CGRect(x: view.frame.size.width/2, y: view.frame.size.height/2, width: 1, height: 1) @@ -288,7 +288,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { guard let node = dataSource.itemIdentifier(for: indexPath), !(node.representedObject is PseudoFeed) else { return nil } - if node.representedObject is Feed { + if node.representedObject is WebFeed { return makeFeedContextMenu(indexPath: indexPath, includeDeleteRename: true) } else { return makeFolderContextMenu(indexPath: indexPath) @@ -508,7 +508,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } } - func discloseFeed(_ feed: Feed, animated: Bool, completion: (() -> Void)? = nil) { + func discloseFeed(_ feed: WebFeed, animated: Bool, completion: (() -> Void)? = nil) { guard let node = coordinator.rootNode.descendantNodeRepresentingObject(feed as AnyObject) else { completion?() @@ -674,14 +674,14 @@ private extension MasterFeedViewController { } func imageFor(_ node: Node) -> IconImage? { - if let feed = node.representedObject as? Feed { + if let webFeed = node.representedObject as? WebFeed { - let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed) + let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed) if feedIconImage != nil { return feedIconImage } - if let faviconImage = appDelegate.faviconDownloader.favicon(for: feed) { + if let faviconImage = appDelegate.faviconDownloader.favicon(for: webFeed) { return faviconImage } @@ -742,7 +742,7 @@ private extension MasterFeedViewController { if let folder = node.representedObject as? Folder { return folder.account } - if let feed = node.representedObject as? Feed { + if let feed = node.representedObject as? WebFeed { return feed.account } return nil @@ -843,7 +843,7 @@ private extension MasterFeedViewController { func copyFeedPageAction(indexPath: IndexPath) -> UIAction? { guard let node = dataSource.itemIdentifier(for: indexPath), - let feed = node.representedObject as? Feed, + let feed = node.representedObject as? WebFeed, let url = URL(string: feed.url) else { return nil } @@ -857,7 +857,7 @@ private extension MasterFeedViewController { func copyFeedPageAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? { guard let node = dataSource.itemIdentifier(for: indexPath), - let feed = node.representedObject as? Feed, + let feed = node.representedObject as? WebFeed, let url = URL(string: feed.url) else { return nil } @@ -872,7 +872,7 @@ private extension MasterFeedViewController { func copyHomePageAction(indexPath: IndexPath) -> UIAction? { guard let node = dataSource.itemIdentifier(for: indexPath), - let feed = node.representedObject as? Feed, + let feed = node.representedObject as? WebFeed, let homePageURL = feed.homePageURL, let url = URL(string: homePageURL) else { return nil @@ -887,7 +887,7 @@ private extension MasterFeedViewController { func copyHomePageAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? { guard let node = dataSource.itemIdentifier(for: indexPath), - let feed = node.representedObject as? Feed, + let feed = node.representedObject as? WebFeed, let homePageURL = feed.homePageURL, let url = URL(string: homePageURL) else { return nil @@ -919,7 +919,7 @@ private extension MasterFeedViewController { } func getInfoAction(indexPath: IndexPath) -> UIAction? { - guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? Feed else { + guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? WebFeed else { return nil } @@ -947,7 +947,7 @@ private extension MasterFeedViewController { } func getInfoAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? { - guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? Feed else { + guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? WebFeed else { return nil } @@ -979,7 +979,7 @@ private extension MasterFeedViewController { return } - if let feed = node.representedObject as? Feed { + if let feed = node.representedObject as? WebFeed { feed.rename(to: name) { result in switch result { case .success: @@ -1023,7 +1023,7 @@ private extension MasterFeedViewController { if let folder = deleteNode.representedObject as? Folder { ActivityManager.cleanUp(folder) - } else if let feed = deleteNode.representedObject as? Feed { + } else if let feed = deleteNode.representedObject as? WebFeed { ActivityManager.cleanUp(feed) } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index c1613e66d..1ff1ad068 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -42,7 +42,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) @@ -316,16 +316,16 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } } - @objc func feedIconDidBecomeAvailable(_ note: Notification) { + @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { titleView?.iconView.iconImage = coordinator.timelineIconImage - guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { + guard let feed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else { return } tableView.indexPathsForVisibleRows?.forEach { indexPath in guard let article = dataSource.itemIdentifier(for: indexPath) else { return } - if article.feed == feed, let cell = tableView.cellForRow(at: indexPath) as? MasterTimelineTableViewCell, let image = iconImageFor(article) { + if article.webFeed == feed, let cell = tableView.cellForRow(at: indexPath) as? MasterTimelineTableViewCell, let image = iconImageFor(article) { cell.setIconImage(image) } } @@ -402,7 +402,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner let prototypeID = "prototype" let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date()) - let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status) + 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, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status) let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize) @@ -464,7 +464,7 @@ private extension MasterTimelineViewController { titleView.label.text = coordinator.timelineName updateTitleUnreadCount() - if coordinator.timelineFetcher is Feed { + if coordinator.timelineFetcher is WebFeed { titleView.heightAnchor.constraint(equalToConstant: 44.0).isActive = true let tap = UITapGestureRecognizer(target: self, action:#selector(showFeedInspector(_:))) titleView.addGestureRecognizer(tap) @@ -525,7 +525,7 @@ private extension MasterTimelineViewController { let showFeedNames = coordinator.showFeedNames let showIcon = coordinator.showIcons && iconImage != nil - cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize) + cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize) } @@ -590,36 +590,36 @@ private extension MasterTimelineViewController { } func discloseFeedAction(_ article: Article) -> UIAction? { - guard let feed = article.feed else { return nil } + guard let webFeed = article.webFeed else { return nil } let title = NSLocalizedString("Go to Feed", comment: "Go to Feed") let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in - self?.coordinator.discloseFeed(feed, animated: true) + self?.coordinator.discloseFeed(webFeed, animated: true) } return action } func discloseFeedAlertAction(_ article: Article, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? { - guard let feed = article.feed else { return nil } + guard let webFeed = article.webFeed else { return nil } let title = NSLocalizedString("Go to Feed", comment: "Go to Feed") let action = UIAlertAction(title: title, style: .default) { [weak self] action in - self?.coordinator.discloseFeed(feed, animated: true) + self?.coordinator.discloseFeed(webFeed, animated: true) completionHandler(true) } return action } func markAllInFeedAsReadAction(_ article: Article) -> UIAction? { - guard let feed = article.feed else { return nil } + guard let webFeed = article.webFeed else { return nil } - let articles = Array(feed.fetchArticles()) + let articles = Array(webFeed.fetchArticles()) guard articles.canMarkAllAsRead() else { return nil } let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command") - let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String + let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String let action = UIAction(title: title, image: AppAssets.markAllInFeedAsReadImage) { [weak self] action in self?.coordinator.markAllAsRead(articles) @@ -628,15 +628,15 @@ private extension MasterTimelineViewController { } func markAllInFeedAsReadAlertAction(_ article: Article, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? { - guard let feed = article.feed else { return nil } + guard let webFeed = article.webFeed else { return nil } - let articles = Array(feed.fetchArticles()) + let articles = Array(webFeed.fetchArticles()) guard articles.canMarkAllAsRead() else { return nil } let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Mark All as Read in Feed") - let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String + let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String let action = UIAlertAction(title: title, style: .default) { [weak self] action in self?.coordinator.markAllAsRead(articles) diff --git a/iOS/Resources/Info.plist b/iOS/Resources/Info.plist index 9eab84870..fa66f10ec 100644 --- a/iOS/Resources/Info.plist +++ b/iOS/Resources/Info.plist @@ -60,7 +60,7 @@ Grant permission to save images from the article. NSUserActivityTypes - AddFeedIntent + AddWebFeedIntent NextUnread ReadArticle SelectFeed diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 2d229b9a8..9ae4e663e 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -88,7 +88,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { set { AppDefaults.displayUndoAvailableTip = newValue } } - private let treeControllerDelegate = FeedTreeControllerDelegate() + private let treeControllerDelegate = WebFeedTreeControllerDelegate() private let treeController: TreeController var stateRestorationActivity: NSUserActivity? { @@ -110,9 +110,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { private(set) var currentFeedIndexPath: IndexPath? var timelineIconImage: IconImage? { - if let feed = timelineFetcher as? Feed { + if let feed = timelineFetcher as? WebFeed { - let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed) + let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: feed) if feedIconImage != nil { return feedIconImage } @@ -135,7 +135,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { timelineMiddleIndexPath = nil - if timelineFetcher is Feed { + if timelineFetcher is WebFeed { showFeedNames = false } else { showFeedNames = true @@ -442,7 +442,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } @objc func accountDidDownloadArticles(_ note: Notification) { - guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set else { + guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set else { return } @@ -607,7 +607,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { masterTimelineViewController?.updateArticleSelection(animated: animated) - if article!.feed?.isArticleExtractorAlwaysOn ?? false { + if article!.webFeed?.isArticleExtractorAlwaysOn ?? false { startArticleExtractorForCurrentLink() currentArticleViewController.state = .loading } else { @@ -780,7 +780,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { markArticlesWithUndo([article], statusKey: .starred, flag: !article.status.starred) } - func discloseFeed(_ feed: Feed, animated: Bool, completion: (() -> Void)? = nil) { + func discloseFeed(_ feed: WebFeed, animated: Bool, completion: (() -> Void)? = nil) { masterFeedViewController.discloseFeed(feed, animated: animated) { completion?() } @@ -806,19 +806,19 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func showFeedInspector() { - guard let feed = timelineFetcher as? Feed else { + guard let feed = timelineFetcher as? WebFeed else { return } showFeedInspector(for: feed) } - func showFeedInspector(for feed: Feed) { + func showFeedInspector(for feed: WebFeed) { let feedInspectorNavController = UIStoryboard.inspector.instantiateViewController(identifier: "FeedInspectorNavigationViewController") as! UINavigationController - let feedInspectorController = feedInspectorNavController.topViewController as! FeedInspectorViewController + let feedInspectorController = feedInspectorNavController.topViewController as! WebFeedInspectorViewController feedInspectorNavController.modalPresentationStyle = .formSheet - feedInspectorNavController.preferredContentSize = FeedInspectorViewController.preferredContentSizeForFormSheetDisplay - feedInspectorController.feed = feed + feedInspectorNavController.preferredContentSize = WebFeedInspectorViewController.preferredContentSizeForFormSheetDisplay + feedInspectorController.webFeed = feed rootSplitViewController.present(feedInspectorNavController, animated: true) } @@ -878,7 +878,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { func homePageURLForFeed(_ indexPath: IndexPath) -> URL? { guard let node = nodeFor(indexPath), - let feed = node.representedObject as? Feed, + let feed = node.representedObject as? WebFeed, let homePageURL = feed.homePageURL, let url = URL(string: homePageURL) else { return nil @@ -1460,19 +1460,19 @@ private extension SceneCoordinator { return false } - func timelineFetcherContainsAnyFeed(_ feeds: Set) -> Bool { + func timelineFetcherContainsAnyFeed(_ feeds: Set) -> Bool { // Return true if there’s a match or if a folder contains (recursively) one of feeds - if let feed = timelineFetcher as? Feed { + if let feed = timelineFetcher as? WebFeed { for oneFeed in feeds { - if feed.feedID == oneFeed.feedID || feed.url == oneFeed.url { + if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url { return true } } } else if let folder = timelineFetcher as? Folder { for oneFeed in feeds { - if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) { + if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) { return true } } @@ -1648,11 +1648,11 @@ private extension SceneCoordinator { selectFeed(indexPath, animated: false) } - case .feed(let accountID, let feedID): - guard let accountNode = findAccountNode(accountID: accountID), let feedNode = findFeedNode(feedID: feedID, beginningAt: accountNode) else { + case .webFeed(let accountID, let webFeedID): + guard let accountNode = findAccountNode(accountID: accountID), let feedNode = findWebFeedNode(webFeedID: webFeedID, beginningAt: accountNode) else { return } - if let feed = feedNode.representedObject as? Feed { + if let feed = feedNode.representedObject as? WebFeed { discloseFeed(feed, animated: false) } @@ -1664,16 +1664,16 @@ private extension SceneCoordinator { let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any], let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String, let accountName = articlePathUserInfo[ArticlePathKey.accountName] as? String, - let feedID = articlePathUserInfo[ArticlePathKey.feedID] as? String, + let webFeedID = articlePathUserInfo[ArticlePathKey.webFeedID] as? String, let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else { return } - guard let accountNode = findAccountNode(accountID: accountID, accountName: accountName), let feedNode = findFeedNode(feedID: feedID, beginningAt: accountNode) else { + guard let accountNode = findAccountNode(accountID: accountID, accountName: accountName), let feedNode = findWebFeedNode(webFeedID: webFeedID, beginningAt: accountNode) else { return } - discloseFeed(feedNode.representedObject as! Feed, animated: false) { + discloseFeed(feedNode.representedObject as! WebFeed, animated: false) { if let article = self.articles.first(where: { $0.articleID == articleID }) { self.selectArticle(article) } @@ -1699,8 +1699,8 @@ private extension SceneCoordinator { return nil } - func findFeedNode(feedID: String, beginningAt startingNode: Node) -> Node? { - if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) { + func findWebFeedNode(webFeedID: String, beginningAt startingNode: Node) -> Node? { + if let node = startingNode.descendantNode(where: { ($0.representedObject as? WebFeed)?.webFeedID == webFeedID }) { return node } return nil diff --git a/iOS/Settings/TimelinePreviewTableViewController.swift b/iOS/Settings/TimelinePreviewTableViewController.swift index 2cec5f49d..34dedc5d9 100644 --- a/iOS/Settings/TimelinePreviewTableViewController.swift +++ b/iOS/Settings/TimelinePreviewTableViewController.swift @@ -67,7 +67,7 @@ private extension TimelinePreviewTableViewController { let prototypeID = "prototype" let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date()) - let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status) + 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, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status) let iconImage = IconImage(AppAssets.faviconTemplateImage.withTintColor(AppAssets.secondaryAccentColor)) diff --git a/iOS/ShareExtension/ShareViewController.swift b/iOS/ShareExtension/ShareViewController.swift index f22ba3fb5..6e929c701 100644 --- a/iOS/ShareExtension/ShareViewController.swift +++ b/iOS/ShareExtension/ShareViewController.swift @@ -108,14 +108,14 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont account = containerAccount } - if let urlString = url?.absoluteString, account!.hasFeed(withURL: urlString) { + if let urlString = url?.absoluteString, account!.hasWebFeed(withURL: urlString) { presentError(AccountError.createErrorAlreadySubscribed) return } let feedName = contentText.isEmpty ? nil : contentText - account!.createFeed(url: url!.absoluteString, name: feedName, container: container!) { result in + account!.createWebFeed(url: url!.absoluteString, name: feedName, container: container!) { result in switch result { case .success: From 55faf550d71281171f8bb557268ddc29224b91b5 Mon Sep 17 00:00:00 2001 From: Kiel Gillard Date: Fri, 15 Nov 2019 19:09:14 +1100 Subject: [PATCH 203/237] Implements logout for Feedly accounts. --- .../Account/Account.xcodeproj/project.pbxproj | 8 + .../Feedly/FeedlyLogoutOperationTests.swift | 216 ++++++++++++++++++ .../Feedly/FeedlyTestSupport.swift | 1 + .../Account/Feedly/FeedlyAPICaller.swift | 36 +++ .../Feedly/FeedlyAccountDelegate.swift | 4 +- .../Operations/FeedlyLogoutOperation.swift | 54 +++++ 6 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 Frameworks/Account/AccountTests/Feedly/FeedlyLogoutOperationTests.swift create mode 100644 Frameworks/Account/Feedly/Operations/FeedlyLogoutOperation.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 090d9d563..af49c7db9 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -111,6 +111,8 @@ 9E713653233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E713652233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift */; }; 9E7299D723505E9600DAEFB7 /* FeedlyAddFeedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7299D623505E9600DAEFB7 /* FeedlyAddFeedOperation.swift */; }; 9E7299D9235062A200DAEFB7 /* FeedlyResourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7299D8235062A200DAEFB7 /* FeedlyResourceProviding.swift */; }; + 9E784EBE237E890600099B1B /* FeedlyLogoutOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */; }; + 9E784EC0237E8BE100099B1B /* FeedlyLogoutOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E784EBF237E8BE100099B1B /* FeedlyLogoutOperationTests.swift */; }; 9E7F88AC235EDDC2009AB9DF /* FeedlyCreateFeedsForCollectionFoldersOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7F88AB235EDDC2009AB9DF /* FeedlyCreateFeedsForCollectionFoldersOperationTests.swift */; }; 9E7F88AE235FBB11009AB9DF /* FeedlyGetStreamContentsOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7F88AD235FBB11009AB9DF /* FeedlyGetStreamContentsOperationTests.swift */; }; 9E84DC472359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E84DC462359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift */; }; @@ -313,6 +315,8 @@ 9E713652233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySetUnreadArticlesOperation.swift; sourceTree = ""; }; 9E7299D623505E9600DAEFB7 /* FeedlyAddFeedOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAddFeedOperation.swift; sourceTree = ""; }; 9E7299D8235062A200DAEFB7 /* FeedlyResourceProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyResourceProviding.swift; sourceTree = ""; }; + 9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyLogoutOperation.swift; sourceTree = ""; }; + 9E784EBF237E8BE100099B1B /* FeedlyLogoutOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyLogoutOperationTests.swift; sourceTree = ""; }; 9E7F88AB235EDDC2009AB9DF /* FeedlyCreateFeedsForCollectionFoldersOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCreateFeedsForCollectionFoldersOperationTests.swift; sourceTree = ""; }; 9E7F88AD235FBB11009AB9DF /* FeedlyGetStreamContentsOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamContentsOperationTests.swift; sourceTree = ""; }; 9E84DC462359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncUnreadStatusesOperation.swift; sourceTree = ""; }; @@ -604,6 +608,7 @@ 9EC804E2236C18AB0057CFCB /* FeedlySyncAllMockResponseProvider.swift */, 9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */, 9E0260CA236FF99A00D122D3 /* FeedlyRefreshAccessTokenOperationTests.swift */, + 9E784EBF237E8BE100099B1B /* FeedlyLogoutOperationTests.swift */, 9E5ABE99236BE6BC00B5DE9F /* feedly-1-initial */, 9EC804E4236C1A7F0057CFCB /* feedly-2-changestatuses */, 9EC804E6236C1BA60057CFCB /* feedly-3-changestatusesagain */, @@ -658,6 +663,7 @@ 9E84DC462359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift */, 9E1D154C233370D800F4944C /* FeedlySyncAllOperation.swift */, 9E672393236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift */, + 9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */, ); path = Operations; sourceTree = ""; @@ -966,6 +972,7 @@ 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, 510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */, 5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */, + 9E784EBE237E890600099B1B /* FeedlyLogoutOperation.swift in Sources */, 9EEEF71F23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift in Sources */, 846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */, 515E4EB72324FF8C0057B0E7 /* Credentials.swift in Sources */, @@ -1043,6 +1050,7 @@ 9EC228572362C7F900766EF8 /* FeedlyCheckpointOperationTests.swift in Sources */, 9E03C122235E62E100FB6D9E /* FeedlyTestSupport.swift in Sources */, 9E3CFFFD2368202000BA7365 /* FeedlySyncUnreadStatusesOperationTests.swift in Sources */, + 9E784EC0237E8BE100099B1B /* FeedlyLogoutOperationTests.swift in Sources */, 9EC228552362C17F00766EF8 /* FeedlySetStarredArticlesOperationTests.swift in Sources */, 9E03C120235E62A500FB6D9E /* FeedlyMirrorCollectionsAsFoldersOperationTests.swift in Sources */, 9E489E912360ED30004372EE /* FeedlyOrganiseParsedItemsByFeedOperationTests.swift in Sources */, diff --git a/Frameworks/Account/AccountTests/Feedly/FeedlyLogoutOperationTests.swift b/Frameworks/Account/AccountTests/Feedly/FeedlyLogoutOperationTests.swift new file mode 100644 index 000000000..95c3c3156 --- /dev/null +++ b/Frameworks/Account/AccountTests/Feedly/FeedlyLogoutOperationTests.swift @@ -0,0 +1,216 @@ +// +// FeedlyLogoutOperationTests.swift +// AccountTests +// +// Created by Kiel Gillard on 15/11/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import XCTest +@testable import Account + +class FeedlyLogoutOperationTests: XCTestCase { + + private var account: Account! + private let support = FeedlyTestSupport() + + override func setUp() { + super.setUp() + account = support.makeTestAccount() + } + + override func tearDown() { + if let account = account { + support.destroy(account) + } + super.tearDown() + } + + private func getTokens(for account: Account) throws -> (accessToken: Credentials, refreshToken: Credentials) { + guard let accessToken = try account.retrieveCredentials(type: .oauthAccessToken), let refreshToken = try account.retrieveCredentials(type: .oauthRefreshToken) else { + XCTFail("Unable to retrieve access and/or refresh token from account.") + throw CredentialsError.incompleteCredentials + } + return (accessToken, refreshToken) + } + + class TestFeedlyLogoutService: FeedlyLogoutService { + var mockResult: Result? + var logoutExpectation: XCTestExpectation? + + func logout(completionHandler: @escaping (Result) -> ()) { + guard let result = mockResult else { + XCTFail("Missing mock result. Test may time out because the completion will not be called.") + return + } + DispatchQueue.main.async { + completionHandler(result) + self.logoutExpectation?.fulfill() + } + } + } + + func testCancel() { + let service = TestFeedlyLogoutService() + service.logoutExpectation = expectation(description: "Did Call Logout") + service.logoutExpectation?.isInverted = true + + let accessToken: Credentials + let refreshToken: Credentials + do { + (accessToken, refreshToken) = try getTokens(for: account) + } catch { + XCTFail("Could not retrieve credentials to verify their integrity later.") + return + } + + let logout = FeedlyLogoutOperation(account: account, service: service, log: support.log) + + // If this expectation is not fulfilled, the operation is not calling `didFinish`. + let completionExpectation = expectation(description: "Did Finish") + logout.completionBlock = { + completionExpectation.fulfill() + } + + OperationQueue.main.addOperation(logout) + + logout.cancel() + + waitForExpectations(timeout: 1) + + XCTAssertTrue(logout.isCancelled) + XCTAssertTrue(logout.isFinished) + + do { + let accountAccessToken = try account.retrieveCredentials(type: .oauthAccessToken) + let accountRefreshToken = try account.retrieveCredentials(type: .oauthRefreshToken) + + XCTAssertEqual(accountAccessToken, accessToken) + XCTAssertEqual(accountRefreshToken, refreshToken) + } catch { + XCTFail("Could not verify tokens were left intact. Did the operation delete them?") + } + } + + func testLogoutSuccess() { + let service = TestFeedlyLogoutService() + service.logoutExpectation = expectation(description: "Did Call Logout") + service.mockResult = .success(()) + + let logout = FeedlyLogoutOperation(account: account, service: service, log: support.log) + + // If this expectation is not fulfilled, the operation is not calling `didFinish`. + let completionExpectation = expectation(description: "Did Finish") + logout.completionBlock = { + completionExpectation.fulfill() + } + + OperationQueue.main.addOperation(logout) + + waitForExpectations(timeout: 1) + + XCTAssertFalse(logout.isCancelled) + + do { + let accountAccessToken = try account.retrieveCredentials(type: .oauthAccessToken) + let accountRefreshToken = try account.retrieveCredentials(type: .oauthRefreshToken) + + XCTAssertNil(accountAccessToken) + XCTAssertNil(accountRefreshToken) + } catch { + XCTFail("Could not verify tokens were deleted.") + } + } + + class TestLogoutDelegate: FeedlyOperationDelegate { + var error: Error? + var didFailExpectation: XCTestExpectation? + + func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) { + self.error = error + didFailExpectation?.fulfill() + } + } + + func testLogoutMissingAccessToken() { + support.removeCredentials(matching: .oauthAccessToken, from: account) + + let (_, service) = support.makeMockNetworkStack() + service.credentials = nil + + let logout = FeedlyLogoutOperation(account: account, service: service, log: support.log) + + let delegate = TestLogoutDelegate() + delegate.didFailExpectation = expectation(description: "Did Fail") + + logout.delegate = delegate + + // If this expectation is not fulfilled, the operation is not calling `didFinish`. + let completionExpectation = expectation(description: "Did Finish") + logout.completionBlock = { + completionExpectation.fulfill() + } + + OperationQueue.main.addOperation(logout) + + waitForExpectations(timeout: 1) + + XCTAssertFalse(logout.isCancelled) + + do { + let accountAccessToken = try account.retrieveCredentials(type: .oauthAccessToken) + XCTAssertNil(accountAccessToken) + } catch { + XCTFail("Could not verify tokens were deleted.") + } + + XCTAssertNotNil(delegate.error, "Should have failed with error.") + if let error = delegate.error { + switch error { + case CredentialsError.incompleteCredentials: + break + default: + XCTFail("Expected \(CredentialsError.incompleteCredentials)") + } + } + } + + func testLogoutFailure() { + let service = TestFeedlyLogoutService() + service.logoutExpectation = expectation(description: "Did Call Logout") + service.mockResult = .failure(URLError(.timedOut)) + + let accessToken: Credentials + let refreshToken: Credentials + do { + (accessToken, refreshToken) = try getTokens(for: account) + } catch { + XCTFail("Could not retrieve credentials to verify their integrity later.") + return + } + + let logout = FeedlyLogoutOperation(account: account, service: service, log: support.log) + + // If this expectation is not fulfilled, the operation is not calling `didFinish`. + let completionExpectation = expectation(description: "Did Finish") + logout.completionBlock = { + completionExpectation.fulfill() + } + + OperationQueue.main.addOperation(logout) + + waitForExpectations(timeout: 1) + + XCTAssertFalse(logout.isCancelled) + + do { + let accountAccessToken = try account.retrieveCredentials(type: .oauthAccessToken) + let accountRefreshToken = try account.retrieveCredentials(type: .oauthRefreshToken) + + XCTAssertEqual(accountAccessToken, accessToken) + XCTAssertEqual(accountRefreshToken, refreshToken) + } catch { + XCTFail("Could not verify tokens were left intact. Did the operation delete them?") + } + } +} diff --git a/Frameworks/Account/AccountTests/Feedly/FeedlyTestSupport.swift b/Frameworks/Account/AccountTests/Feedly/FeedlyTestSupport.swift index ea1e79f26..afa84b3c2 100644 --- a/Frameworks/Account/AccountTests/Feedly/FeedlyTestSupport.swift +++ b/Frameworks/Account/AccountTests/Feedly/FeedlyTestSupport.swift @@ -77,6 +77,7 @@ class FeedlyTestSupport { func destroy(_ testAccount: Account) { do { + // These should not throw when the keychain items are not found. try testAccount.removeCredentials(type: .oauthAccessToken) try testAccount.removeCredentials(type: .oauthRefreshToken) } catch { diff --git a/Frameworks/Account/Feedly/FeedlyAPICaller.swift b/Frameworks/Account/Feedly/FeedlyAPICaller.swift index 38fd68734..29cfa8ff1 100644 --- a/Frameworks/Account/Feedly/FeedlyAPICaller.swift +++ b/Frameworks/Account/Feedly/FeedlyAPICaller.swift @@ -680,3 +680,39 @@ extension FeedlyAPICaller: FeedlyMarkArticlesService { } } } + +extension FeedlyAPICaller: FeedlyLogoutService { + + func logout(completionHandler: @escaping (Result) -> ()) { + guard let accessToken = credentials?.secret else { + return DispatchQueue.main.async { + completionHandler(.failure(CredentialsError.incompleteCredentials)) + } + } + var components = baseUrlComponents + components.path = "/v3/auth/logout" + + guard let url = components.url else { + fatalError("\(components) does not produce a valid URL.") + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType) + request.addValue("application/json", forHTTPHeaderField: "Accept-Type") + request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization) + + transport.send(request: request, resultType: String.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in + switch result { + case .success(let (httpResponse, _)): + if httpResponse.statusCode == 200 { + completionHandler(.success(())) + } else { + completionHandler(.failure(URLError(.cannotDecodeContentData))) + } + case .failure(let error): + completionHandler(.failure(error)) + } + } + } +} diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index dcca708d0..8f8a0e588 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -487,7 +487,9 @@ final class FeedlyAccountDelegate: AccountDelegate { } func accountWillBeDeleted(_ account: Account) { - + let logout = FeedlyLogoutOperation(account: account, service: caller, log: log) + // Dispatch on the main queue because the lifetime of the account delegate is uncertain. + OperationQueue.main.addOperation(logout) } static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result) -> Void) { diff --git a/Frameworks/Account/Feedly/Operations/FeedlyLogoutOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlyLogoutOperation.swift new file mode 100644 index 000000000..53e640799 --- /dev/null +++ b/Frameworks/Account/Feedly/Operations/FeedlyLogoutOperation.swift @@ -0,0 +1,54 @@ +// +// FeedlyLogoutOperation.swift +// Account +// +// Created by Kiel Gillard on 15/11/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation +import os.log + +protocol FeedlyLogoutService { + func logout(completionHandler: @escaping (Result) -> ()) +} + +final class FeedlyLogoutOperation: FeedlyOperation { + let service: FeedlyLogoutService + let account: Account + let log: OSLog + + init(account: Account, service: FeedlyLogoutService, log: OSLog) { + self.service = service + self.account = account + self.log = log + } + + override func main() { + guard !isCancelled else { + didFinish() + return + } + os_log("Requesting logout of %{public}@ account.", "\(account.type)") + service.logout(completionHandler: didCompleteLogout(_:)) + } + + func didCompleteLogout(_ result: Result) { + assert(Thread.isMainThread) + switch result { + case .success: + os_log("Logged out of %{public}@ account.", "\(account.type)") + do { + try account.removeCredentials(type: .oauthAccessToken) + try account.removeCredentials(type: .oauthRefreshToken) + } catch { + // oh well, we tried our best. + } + didFinish() + + case .failure(let error): + os_log("Logout failed because %{public}@.", error as NSError) + didFinish(error) + } + } +} From 5283d2efbea5ce8315f7dc3470d896fc8528ea47 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 15 Nov 2019 06:19:14 -0600 Subject: [PATCH 204/237] Implement Feed protocol. --- .../Account/Account.xcodeproj/project.pbxproj | 16 +++-- Frameworks/Account/ArticleFetcher.swift | 18 ------ Frameworks/Account/Feed.swift | 14 +++++ ...FetcherType.swift => FeedIdentifier.swift} | 14 +++-- Frameworks/Account/Folder.swift | 10 +++- Frameworks/Account/WebFeed.swift | 10 +++- Mac/MainWindow/MainWindowController.swift | 2 +- Shared/Activity/ActivityManager.swift | 29 +++++---- Shared/SmartFeeds/PseudoFeed.swift | 5 +- Shared/SmartFeeds/SearchFeedDelegate.swift | 4 +- .../SearchTimelineFeedDelegate.swift | 4 +- Shared/SmartFeeds/SmartFeed.swift | 8 +-- Shared/SmartFeeds/SmartFeedDelegate.swift | 4 +- Shared/SmartFeeds/StarredFeedDelegate.swift | 4 +- Shared/SmartFeeds/TodayFeedDelegate.swift | 4 +- Shared/SmartFeeds/UnreadFeed.swift | 8 +-- iOS/MasterFeed/MasterFeedViewController.swift | 2 +- .../MasterTimelineViewController.swift | 10 ++-- iOS/SceneCoordinator.swift | 60 +++++++++---------- 19 files changed, 119 insertions(+), 107 deletions(-) create mode 100644 Frameworks/Account/Feed.swift rename Frameworks/Account/{ArticleFetcherType.swift => FeedIdentifier.swift} (85%) diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index af49c7db9..f5c99af54 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ 5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; }; 510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; }; 510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */; }; - 511B9804237CD4270028BCAA /* ArticleFetcherType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */; }; + 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* FeedIdentifier.swift */; }; 513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; }; 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; }; 5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; }; @@ -32,6 +32,7 @@ 5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D73022837F3400D9D53D /* InitialFeedDownloader.swift */; }; 5170743C232AEDB500A461A3 /* OPMLFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5170743B232AEDB500A461A3 /* OPMLFile.swift */; }; 51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */; }; + 51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC8FCB237EC055004F8B56 /* Feed.swift */; }; 51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D58754227F53BE00900287 /* FeedbinTag.swift */; }; 51D5875A227F630B00900287 /* tags_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58757227F630B00900287 /* tags_delete.json */; }; 51D5875B227F630B00900287 /* tags_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58758227F630B00900287 /* tags_add.json */; }; @@ -212,7 +213,7 @@ 5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = ""; }; 510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = ""; }; 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadataFile.swift; sourceTree = ""; }; - 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleFetcherType.swift; sourceTree = ""; }; + 511B9803237CD4270028BCAA /* FeedIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIdentifier.swift; sourceTree = ""; }; 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = ""; }; 513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = ""; }; 5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = ""; }; @@ -233,6 +234,7 @@ 5170743B232AEDB500A461A3 /* OPMLFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLFile.swift; sourceTree = ""; }; 518B2EA52351306200400001 /* Account_project_test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_test.xcconfig; sourceTree = ""; }; 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBehaviors.swift; sourceTree = ""; }; + 51BC8FCB237EC055004F8B56 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = ""; }; 51D58754227F53BE00900287 /* FeedbinTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTag.swift; sourceTree = ""; }; 51D58757227F630B00900287 /* tags_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_delete.json; sourceTree = ""; }; 51D58758227F630B00900287 /* tags_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_add.json; sourceTree = ""; }; @@ -531,16 +533,17 @@ 84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */, 510BD110232C3801002692E4 /* AccountMetadataFile.swift */, 84F73CF0202788D80000BCEF /* ArticleFetcher.swift */, - 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */, 84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */, 8419740D1F6DD25F006346C4 /* Container.swift */, 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */, 84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */, + 51BC8FCB237EC055004F8B56 /* Feed.swift */, + 511B9803237CD4270028BCAA /* FeedIdentifier.swift */, + 841974001F6DD1EC006346C4 /* Folder.swift */, + 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */, 844B297C2106C7EC004020B3 /* WebFeed.swift */, 84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */, 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */, - 841974001F6DD1EC006346C4 /* Folder.swift */, - 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */, 5165D71F22835E9800D9D53D /* FeedFinder */, 515E4EB12324FF7D0057B0E7 /* Credentials */, 8419742B1F6DDE84006346C4 /* LocalAccount */, @@ -966,7 +969,7 @@ 9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */, 552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */, 9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */, - 511B9804237CD4270028BCAA /* ArticleFetcherType.swift in Sources */, + 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */, 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */, 9E713653233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift in Sources */, 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, @@ -1002,6 +1005,7 @@ 5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */, 84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */, 9E510D6E234F16A8002E6F1A /* FeedlyAddFeedRequest.swift in Sources */, + 51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */, 846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */, 55203300229D5D5A009559E0 /* ReaderAPICaller.swift in Sources */, 9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */, diff --git a/Frameworks/Account/ArticleFetcher.swift b/Frameworks/Account/ArticleFetcher.swift index 43509b866..4bf3f647e 100644 --- a/Frameworks/Account/ArticleFetcher.swift +++ b/Frameworks/Account/ArticleFetcher.swift @@ -11,8 +11,6 @@ import Articles public protocol ArticleFetcher { - var articleFetcherType: ArticleFetcherType? { get } - func fetchArticles() -> Set
func fetchArticlesAsync(_ callback: @escaping ArticleSetBlock) func fetchUnreadArticles() -> Set
@@ -21,14 +19,6 @@ public protocol ArticleFetcher { extension WebFeed: ArticleFetcher { - public var articleFetcherType: ArticleFetcherType? { - guard let accountID = account?.accountID else { - assertionFailure("Expected feed.account, but got nil.") - return nil - } - return ArticleFetcherType.webFeed(accountID, webFeedID) - } - public func fetchArticles() -> Set
{ return account?.fetchArticles(.webFeed(self)) ?? Set
() } @@ -57,14 +47,6 @@ extension WebFeed: ArticleFetcher { } extension Folder: ArticleFetcher { - - public var articleFetcherType: ArticleFetcherType? { - guard let accountID = account?.accountID else { - assertionFailure("Expected feed.account, but got nil.") - return nil - } - return ArticleFetcherType.folder(accountID, nameForDisplay) - } public func fetchArticles() -> Set
{ return fetchUnreadArticles() diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/Feed.swift new file mode 100644 index 000000000..3fcf1e3d2 --- /dev/null +++ b/Frameworks/Account/Feed.swift @@ -0,0 +1,14 @@ +// +// Feed.swift +// Account +// +// Created by Maurice Parker on 11/15/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation +import RSCore + +public protocol Feed: FeedIdentifiable, ArticleFetcher, DisplayNameProvider, UnreadCountProvider { + +} diff --git a/Frameworks/Account/ArticleFetcherType.swift b/Frameworks/Account/FeedIdentifier.swift similarity index 85% rename from Frameworks/Account/ArticleFetcherType.swift rename to Frameworks/Account/FeedIdentifier.swift index 02912fb5f..6b462b99f 100644 --- a/Frameworks/Account/ArticleFetcherType.swift +++ b/Frameworks/Account/FeedIdentifier.swift @@ -8,7 +8,11 @@ import Foundation -public enum ArticleFetcherType: CustomStringConvertible { +public protocol FeedIdentifiable { + var feedID: FeedIdentifier? { get } +} + +public enum FeedIdentifier: CustomStringConvertible { case smartFeed(String) // String is a unique identifier case script(String) // String is a unique identifier @@ -61,16 +65,16 @@ public enum ArticleFetcherType: CustomStringConvertible { switch type { case "smartFeed": guard let id = userInfo["id"] as? String else { return nil } - self = ArticleFetcherType.smartFeed(id) + self = FeedIdentifier.smartFeed(id) case "script": guard let id = userInfo["id"] as? String else { return nil } - self = ArticleFetcherType.script(id) + self = FeedIdentifier.script(id) case "feed": guard let accountID = userInfo["accountID"] as? String, let webFeedID = userInfo["webFeedID"] as? String else { return nil } - self = ArticleFetcherType.webFeed(accountID, webFeedID) + self = FeedIdentifier.webFeed(accountID, webFeedID) case "folder": guard let accountID = userInfo["accountID"] as? String, let folderName = userInfo["folderName"] as? String else { return nil } - self = ArticleFetcherType.folder(accountID, folderName) + self = FeedIdentifier.folder(accountID, folderName) default: return nil } diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index 47b4292b6..cf6f1df57 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -10,7 +10,15 @@ import Foundation import Articles import RSCore -public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCountProvider, Hashable { +public final class Folder: Feed, Renamable, Container, Hashable { + + public var feedID: FeedIdentifier? { + guard let accountID = account?.accountID else { + assertionFailure("Expected feed.account, but got nil.") + return nil + } + return FeedIdentifier.folder(accountID, nameForDisplay) + } public weak var account: Account? public var topLevelWebFeeds: Set = Set() diff --git a/Frameworks/Account/WebFeed.swift b/Frameworks/Account/WebFeed.swift index 1ceb79bdc..929701123 100644 --- a/Frameworks/Account/WebFeed.swift +++ b/Frameworks/Account/WebFeed.swift @@ -11,7 +11,15 @@ import RSCore import RSWeb import Articles -public final class WebFeed: DisplayNameProvider, Renamable, UnreadCountProvider, Hashable { +public final class WebFeed: Feed, Renamable, Hashable { + + public var feedID: FeedIdentifier? { + guard let accountID = account?.accountID else { + assertionFailure("Expected feed.account, but got nil.") + return nil + } + return FeedIdentifier.webFeed(accountID, webFeedID) + } public weak var account: Account? public let url: String diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 7bf7ea6b5..ff050a501 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -481,7 +481,7 @@ extension MainWindowController: TimelineContainerViewControllerDelegate { let detailState: DetailState if let articles = articles { if articles.count == 1 { - activityManager.reading(fetcher: nil, article: articles.first) + activityManager.reading(feed: nil, article: articles.first) if articles.first?.webFeed?.isArticleExtractorAlwaysOn ?? false { detailState = .loading startArticleExtractorForCurrentLink() diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift index dc1b216a8..25403da8f 100644 --- a/Shared/Activity/ActivityManager.swift +++ b/Shared/Activity/ActivityManager.swift @@ -38,13 +38,13 @@ class ActivityManager { invalidateNextUnread() } - func selecting(fetcher: ArticleFetcher) { + func selecting(feed: Feed) { invalidateCurrentActivities() - selectingActivity = makeSelectFeedActivity(fetcher: fetcher) + selectingActivity = makeSelectFeedActivity(feed: feed) - if let feed = fetcher as? WebFeed { - updateSelectingActivityFeedSearchAttributes(with: feed) + if let webFeed = feed as? WebFeed { + updateSelectingActivityFeedSearchAttributes(with: webFeed) } donate(selectingActivity!) @@ -76,12 +76,12 @@ class ActivityManager { nextUnreadActivity = nil } - func reading(fetcher: ArticleFetcher?, article: Article?) { + func reading(feed: Feed?, article: Article?) { invalidateReading() invalidateNextUnread() guard let article = article else { return } - readingActivity = makeReadArticleActivity(fetcher: fetcher, article: article) + readingActivity = makeReadArticleActivity(feed: feed, article: article) #if os(iOS) updateReadArticleSearchAttributes(with: article) @@ -151,37 +151,36 @@ class ActivityManager { private extension ActivityManager { - func makeSelectFeedActivity(fetcher: ArticleFetcher) -> NSUserActivity { + func makeSelectFeedActivity(feed: Feed) -> NSUserActivity { let activity = NSUserActivity(activityType: ActivityType.selectFeed.rawValue) let localizedText = NSLocalizedString("See articles in “%@”", comment: "See articles in Folder") - let displayName = (fetcher as? DisplayNameProvider)?.nameForDisplay ?? "" - let title = NSString.localizedStringWithFormat(localizedText as NSString, displayName) as String + let title = NSString.localizedStringWithFormat(localizedText as NSString, feed.nameForDisplay) as String activity.title = title activity.keywords = Set(makeKeywords(title)) activity.isEligibleForSearch = true - let articleFetcherIdentifierUserInfo = fetcher.articleFetcherType?.userInfo ?? [AnyHashable: Any]() + let articleFetcherIdentifierUserInfo = feed.feedID?.userInfo ?? [AnyHashable: Any]() activity.userInfo = [UserInfoKey.feedIdentifier: articleFetcherIdentifierUserInfo] activity.requiredUserInfoKeys = Set(activity.userInfo!.keys.map { $0 as! String }) #if os(iOS) activity.suggestedInvocationPhrase = title activity.isEligibleForPrediction = true - activity.persistentIdentifier = fetcher.articleFetcherType?.description ?? "" - activity.contentAttributeSet?.relatedUniqueIdentifier = fetcher.articleFetcherType?.description ?? "" + activity.persistentIdentifier = feed.feedID?.description ?? "" + activity.contentAttributeSet?.relatedUniqueIdentifier = feed.feedID?.description ?? "" #endif return activity } - func makeReadArticleActivity(fetcher: ArticleFetcher?, article: Article) -> NSUserActivity { + func makeReadArticleActivity(feed: Feed?, article: Article) -> NSUserActivity { let activity = NSUserActivity(activityType: ActivityType.readArticle.rawValue) activity.title = ArticleStringFormatter.truncatedTitle(article) - if let fetcher = fetcher { - let articleFetcherIdentifierUserInfo = fetcher.articleFetcherType?.userInfo ?? [AnyHashable: Any]() + if let feed = feed { + let articleFetcherIdentifierUserInfo = feed.feedID?.userInfo ?? [AnyHashable: Any]() let articlePathUserInfo = article.pathUserInfo activity.userInfo = [UserInfoKey.feedIdentifier: articleFetcherIdentifierUserInfo, UserInfoKey.articlePath: articlePathUserInfo] } else { diff --git a/Shared/SmartFeeds/PseudoFeed.swift b/Shared/SmartFeeds/PseudoFeed.swift index 4d2e323ad..8a9c7769d 100644 --- a/Shared/SmartFeeds/PseudoFeed.swift +++ b/Shared/SmartFeeds/PseudoFeed.swift @@ -13,12 +13,11 @@ import Articles import Account import RSCore -protocol PseudoFeed: class, DisplayNameProvider, UnreadCountProvider, SmallIconProvider, PasteboardWriterOwner { +protocol PseudoFeed: class, Feed, SmallIconProvider, PasteboardWriterOwner { } private var smartFeedIcon: RSImage = { - return RSImage(named: NSImage.smartBadgeTemplateName)! }() @@ -35,7 +34,7 @@ import Articles import Account import RSCore -protocol PseudoFeed: class, DisplayNameProvider, UnreadCountProvider, SmallIconProvider { +protocol PseudoFeed: class, Feed, SmallIconProvider { } diff --git a/Shared/SmartFeeds/SearchFeedDelegate.swift b/Shared/SmartFeeds/SearchFeedDelegate.swift index 6b1cdaca4..aacbc704c 100644 --- a/Shared/SmartFeeds/SearchFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchFeedDelegate.swift @@ -13,8 +13,8 @@ import Articles struct SearchFeedDelegate: SmartFeedDelegate { - var articleFetcherType: ArticleFetcherType? { - return ArticleFetcherType.smartFeed(String(describing: SearchFeedDelegate.self)) + var feedID: FeedIdentifier? { + return FeedIdentifier.smartFeed(String(describing: SearchFeedDelegate.self)) } var nameForDisplay: String { diff --git a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift index 3df0bc222..3d2af5fb5 100644 --- a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift @@ -13,8 +13,8 @@ import Articles struct SearchTimelineFeedDelegate: SmartFeedDelegate { - var articleFetcherType: ArticleFetcherType? { - return ArticleFetcherType.smartFeed(String(describing: SearchTimelineFeedDelegate.self)) + var feedID: FeedIdentifier? { + return FeedIdentifier.smartFeed(String(describing: SearchTimelineFeedDelegate.self)) } var nameForDisplay: String { diff --git a/Shared/SmartFeeds/SmartFeed.swift b/Shared/SmartFeeds/SmartFeed.swift index aef721a58..0419c47a1 100644 --- a/Shared/SmartFeeds/SmartFeed.swift +++ b/Shared/SmartFeeds/SmartFeed.swift @@ -13,6 +13,10 @@ import Account final class SmartFeed: PseudoFeed { + var feedID: FeedIdentifier? { + delegate.feedID + } + var nameForDisplay: String { return delegate.nameForDisplay } @@ -71,10 +75,6 @@ final class SmartFeed: PseudoFeed { } extension SmartFeed: ArticleFetcher { - - var articleFetcherType: ArticleFetcherType? { - delegate.articleFetcherType - } func fetchArticles() -> Set
{ return delegate.fetchArticles() diff --git a/Shared/SmartFeeds/SmartFeedDelegate.swift b/Shared/SmartFeeds/SmartFeedDelegate.swift index a39d4b411..2f831a8e8 100644 --- a/Shared/SmartFeeds/SmartFeedDelegate.swift +++ b/Shared/SmartFeeds/SmartFeedDelegate.swift @@ -11,10 +11,8 @@ import Account import Articles import RSCore -protocol SmartFeedDelegate: DisplayNameProvider, ArticleFetcher, SmallIconProvider { - +protocol SmartFeedDelegate: FeedIdentifiable, DisplayNameProvider, ArticleFetcher, SmallIconProvider { var fetchType: FetchType { get } - func fetchUnreadCount(for: Account, callback: @escaping (Int) -> Void) } diff --git a/Shared/SmartFeeds/StarredFeedDelegate.swift b/Shared/SmartFeeds/StarredFeedDelegate.swift index df4ad368b..81f05fbf9 100644 --- a/Shared/SmartFeeds/StarredFeedDelegate.swift +++ b/Shared/SmartFeeds/StarredFeedDelegate.swift @@ -15,8 +15,8 @@ import Account struct StarredFeedDelegate: SmartFeedDelegate { - var articleFetcherType: ArticleFetcherType? { - return ArticleFetcherType.smartFeed(String(describing: StarredFeedDelegate.self)) + var feedID: FeedIdentifier? { + return FeedIdentifier.smartFeed(String(describing: StarredFeedDelegate.self)) } let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title") diff --git a/Shared/SmartFeeds/TodayFeedDelegate.swift b/Shared/SmartFeeds/TodayFeedDelegate.swift index 37f67723f..0bd70f653 100644 --- a/Shared/SmartFeeds/TodayFeedDelegate.swift +++ b/Shared/SmartFeeds/TodayFeedDelegate.swift @@ -13,8 +13,8 @@ import Account struct TodayFeedDelegate: SmartFeedDelegate { - var articleFetcherType: ArticleFetcherType? { - return ArticleFetcherType.smartFeed(String(describing: TodayFeedDelegate.self)) + var feedID: FeedIdentifier? { + return FeedIdentifier.smartFeed(String(describing: TodayFeedDelegate.self)) } let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title") diff --git a/Shared/SmartFeeds/UnreadFeed.swift b/Shared/SmartFeeds/UnreadFeed.swift index 8955597a2..f41793988 100644 --- a/Shared/SmartFeeds/UnreadFeed.swift +++ b/Shared/SmartFeeds/UnreadFeed.swift @@ -19,6 +19,10 @@ import Articles final class UnreadFeed: PseudoFeed { + var feedID: FeedIdentifier? { + return FeedIdentifier.smartFeed(String(describing: UnreadFeed.self)) + } + let nameForDisplay = NSLocalizedString("All Unread", comment: "All Unread pseudo-feed title") let fetchType = FetchType.unread @@ -53,10 +57,6 @@ final class UnreadFeed: PseudoFeed { extension UnreadFeed: ArticleFetcher { - var articleFetcherType: ArticleFetcherType? { - return ArticleFetcherType.smartFeed(String(describing: UnreadFeed.self)) - } - func fetchArticles() -> Set
{ return fetchUnreadArticles() } diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index b0acd64ba..96010c8b6 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -93,7 +93,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } var node: Node? = nil - if let coordinator = representedObject as? SceneCoordinator, let fetcher = coordinator.timelineFetcher { + if let coordinator = representedObject as? SceneCoordinator, let fetcher = coordinator.timelineFeed { node = coordinator.rootNode.descendantNodeRepresentingObject(fetcher as AnyObject) } else { node = coordinator.rootNode.descendantNodeRepresentingObject(representedObject as AnyObject) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 1ff1ad068..ec9cce0a0 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -368,7 +368,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } @objc func displayNameDidChange(_ note: Notification) { - titleView?.label.text = coordinator.timelineName + titleView?.label.text = coordinator.timelineFeed?.nameForDisplay } @objc func scrollPositionDidChange() { @@ -455,16 +455,16 @@ extension MasterTimelineViewController: UISearchBarDelegate { private extension MasterTimelineViewController { func resetUI() { - title = coordinator.timelineName + title = coordinator.timelineFeed?.nameForDisplay if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView { self.titleView = titleView titleView.iconView.iconImage = coordinator.timelineIconImage - titleView.label.text = coordinator.timelineName + titleView.label.text = coordinator.timelineFeed?.nameForDisplay updateTitleUnreadCount() - if coordinator.timelineFetcher is WebFeed { + if coordinator.timelineFeed is WebFeed { titleView.heightAnchor.constraint(equalToConstant: 44.0).isActive = true let tap = UITapGestureRecognizer(target: self, action:#selector(showFeedInspector(_:))) titleView.addGestureRecognizer(tap) @@ -494,7 +494,7 @@ private extension MasterTimelineViewController { } func updateTitleUnreadCount() { - if let unreadCountProvider = coordinator.timelineFetcher as? UnreadCountProvider { + if let unreadCountProvider = coordinator.timelineFeed as? UnreadCountProvider { self.titleView?.unreadCountView.unreadCount = unreadCountProvider.unreadCount } } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 9ae4e663e..21de1824d 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -110,7 +110,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { private(set) var currentFeedIndexPath: IndexPath? var timelineIconImage: IconImage? { - if let feed = timelineFetcher as? WebFeed { + if let feed = timelineFeed as? WebFeed { let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: feed) if feedIconImage != nil { @@ -123,19 +123,15 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } - return (timelineFetcher as? SmallIconProvider)?.smallIcon + return (timelineFeed as? SmallIconProvider)?.smallIcon } - var timelineName: String? { - return (timelineFetcher as? DisplayNameProvider)?.nameForDisplay - } - - var timelineFetcher: ArticleFetcher? { + var timelineFeed: Feed? { didSet { timelineMiddleIndexPath = nil - if timelineFetcher is WebFeed { + if timelineFeed is WebFeed { showFeedNames = false } else { showFeedNames = true @@ -259,7 +255,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } var isTimelineUnreadAvailable: Bool { - if let unreadProvider = timelineFetcher as? UnreadCountProvider { + if let unreadProvider = timelineFeed as? UnreadCountProvider { return unreadProvider.unreadCount > 0 } return false @@ -519,7 +515,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func masterFeedIndexPathForCurrentTimeline() -> IndexPath? { - guard let node = treeController.rootNode.descendantNodeRepresentingObject(timelineFetcher as AnyObject) else { + guard let node = treeController.rootNode.descendantNodeRepresentingObject(timelineFeed as AnyObject) else { return nil } return indexPathFor(node) @@ -533,12 +529,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { masterFeedViewController.updateFeedSelection(animated: animated) - if let ip = indexPath, let node = nodeFor(ip), let fetcher = node.representedObject as? ArticleFetcher { - timelineFetcher = fetcher - activityManager.selecting(fetcher: fetcher) + if let ip = indexPath, let node = nodeFor(ip), let feed = node.representedObject as? Feed { + timelineFeed = feed + activityManager.selecting(feed: feed) installTimelineControllerIfNecessary(animated: animated) } else { - timelineFetcher = nil + timelineFeed = nil activityManager.invalidateSelecting() if rootSplitViewController.isCollapsed && navControllerForTimeline().viewControllers.last is MasterTimelineViewController { navControllerForTimeline().popViewController(animated: animated) @@ -582,7 +578,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { stopArticleExtractor() currentArticle = article - activityManager.reading(fetcher: timelineFetcher, article: article) + activityManager.reading(feed: timelineFeed, article: article) if article == nil { if rootSplitViewController.isCollapsed { @@ -621,7 +617,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { func beginSearching() { isSearching = true searchArticleIds = Set(articles.map { $0.articleID }) - timelineFetcher = nil + timelineFeed = nil } func endSearching() { @@ -630,10 +626,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { lastSearchScope = nil searchArticleIds = nil - if let ip = currentFeedIndexPath, let node = nodeFor(ip), let fetcher = node.representedObject as? ArticleFetcher { - timelineFetcher = fetcher + if let ip = currentFeedIndexPath, let node = nodeFor(ip), let feed = node.representedObject as? Feed { + timelineFeed = feed } else { - timelineFetcher = nil + timelineFeed = nil } selectArticle(nil) @@ -644,7 +640,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { guard isSearching else { return } if searchString.count < 3 { - timelineFetcher = nil + timelineFeed = nil return } @@ -652,9 +648,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { switch searchScope { case .global: - timelineFetcher = SmartFeed(delegate: SearchFeedDelegate(searchString: searchString)) + timelineFeed = SmartFeed(delegate: SearchFeedDelegate(searchString: searchString)) case .timeline: - timelineFetcher = SmartFeed(delegate: SearchTimelineFeedDelegate(searchString: searchString, articleIDs: searchArticleIds!)) + timelineFeed = SmartFeed(delegate: SearchTimelineFeedDelegate(searchString: searchString, articleIDs: searchArticleIds!)) } lastSearchString = searchString @@ -806,7 +802,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func showFeedInspector() { - guard let feed = timelineFetcher as? WebFeed else { + guard let feed = timelineFeed as? WebFeed else { return } showFeedInspector(for: feed) @@ -1362,11 +1358,11 @@ private extension SceneCoordinator { @objc func fetchAndMergeArticles() { - guard let timelineFetcher = timelineFetcher else { + guard let timelineFeed = timelineFeed else { return } - fetchUnsortedArticlesAsync(for: [timelineFetcher]) { [weak self] (unsortedArticles) in + fetchUnsortedArticlesAsync(for: [timelineFeed]) { [weak self] (unsortedArticles) in // Merge articles by articleID. For any unique articleID in current articles, add to unsortedArticles. guard let strongSelf = self else { return @@ -1395,7 +1391,7 @@ private extension SceneCoordinator { // so that the entire display refreshes at once. // It’s a better user experience this way. cancelPendingAsyncFetches() - guard let timelineFetcher = timelineFetcher else { + guard let timelineFetcher = timelineFeed else { emptyTheTimeline() return } @@ -1407,7 +1403,7 @@ private extension SceneCoordinator { // To be called when we need to do an entire fetch, but an async delay is okay. // Example: we have the Today feed selected, and the calendar day just changed. cancelPendingAsyncFetches() - guard let timelineFetcher = timelineFetcher else { + guard let timelineFetcher = timelineFeed else { emptyTheTimeline() return } @@ -1447,14 +1443,14 @@ private extension SceneCoordinator { } func timelineFetcherContainsAnyPseudoFeed() -> Bool { - if timelineFetcher is PseudoFeed { + if timelineFeed is PseudoFeed { return true } return false } func timelineFetcherContainsAnyFolder() -> Bool { - if timelineFetcher is Folder { + if timelineFeed is Folder { return true } return false @@ -1464,13 +1460,13 @@ private extension SceneCoordinator { // Return true if there’s a match or if a folder contains (recursively) one of feeds - if let feed = timelineFetcher as? WebFeed { + if let feed = timelineFeed as? WebFeed { for oneFeed in feeds { if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url { return true } } - } else if let folder = timelineFetcher as? Folder { + } else if let folder = timelineFeed as? Folder { for oneFeed in feeds { if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) { return true @@ -1625,7 +1621,7 @@ private extension SceneCoordinator { func handleSelectFeed(_ userInfo: [AnyHashable : Any]?) { guard let userInfo = userInfo, let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : Any], - let articleFetcherType = ArticleFetcherType(userInfo: feedIdentifierUserInfo) else { + let articleFetcherType = FeedIdentifier(userInfo: feedIdentifierUserInfo) else { return } From d3e59852581efde5f2f5864260e7ea28ac163b2e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 15 Nov 2019 15:46:43 -0600 Subject: [PATCH 205/237] Update AppleScript to differentiate between WebFeeds and Feeds --- ...reateFeedStatisticsSpreadsheet.applescript | 2 +- .../Safari-OpenAllStarredArticles.applescript | 2 +- Mac/Resources/NetNewsWire.sdef | 26 ++--- Mac/Scriptability/Account+Scriptability.swift | 36 +++---- .../AppDelegate+Scriptability.swift | 4 +- Mac/Scriptability/Folder+Scriptability.swift | 8 +- .../NSApplication+Scriptability.swift | 24 ++--- ...lity.swift => WebFeed+Scriptability.swift} | 16 ++-- NetNewsWire.xcodeproj/project.pbxproj | 12 +-- .../NetNewsWireTests/ArticleSorterTests.swift | 94 +++++++++---------- .../scripts/testFeedExists.applescript | 2 +- .../scripts/testFeedOPML.applescript | 2 +- .../testNameAndUrlOfEveryFeed.applescript | 2 +- .../scripts/testNameOfAuthors.applescript | 2 +- Tests/NetNewsWireTests/SharingTests.swift | 19 +--- 15 files changed, 117 insertions(+), 134 deletions(-) rename Mac/Scriptability/{Feed+Scriptability.swift => WebFeed+Scriptability.swift} (93%) diff --git a/AppleScript/Excel-CreateFeedStatisticsSpreadsheet.applescript b/AppleScript/Excel-CreateFeedStatisticsSpreadsheet.applescript index b3fe5533a..9f738094f 100644 --- a/AppleScript/Excel-CreateFeedStatisticsSpreadsheet.applescript +++ b/AppleScript/Excel-CreateFeedStatisticsSpreadsheet.applescript @@ -36,7 +36,7 @@ set totalFeeds to 0 tell application "NetNewsWire" set allAccounts to every account repeat with nthAccount in allAccounts - set allFeeds to every feed of nthAccount + set allFeeds to every webFeed of nthAccount repeat with nthFeed in allFeeds set feedname to name of nthFeed set articleCount to count (get every article of nthFeed) diff --git a/AppleScript/Safari-OpenAllStarredArticles.applescript b/AppleScript/Safari-OpenAllStarredArticles.applescript index cad33bb4d..a1cba2ec0 100644 --- a/AppleScript/Safari-OpenAllStarredArticles.applescript +++ b/AppleScript/Safari-OpenAllStarredArticles.applescript @@ -38,7 +38,7 @@ set safariWindow to missing value tell application "NetNewsWire" set allAccounts to every account repeat with nthAccount in allAccounts - set allFeeds to every feed of nthAccount + set allFeeds to every webFeed of nthAccount repeat with nthFeed in allFeeds set starredArticles to (get every article of nthFeed where starred is true) repeat with nthArticle in starredArticles diff --git a/Mac/Resources/NetNewsWire.sdef b/Mac/Resources/NetNewsWire.sdef index 3f445d602..ab63c5db3 100644 --- a/Mac/Resources/NetNewsWire.sdef +++ b/Mac/Resources/NetNewsWire.sdef @@ -60,8 +60,8 @@ - - + + @@ -88,30 +88,30 @@ - - - + + + - - + + - - + + - + - + @@ -164,8 +164,8 @@ - - + + diff --git a/Mac/Scriptability/Account+Scriptability.swift b/Mac/Scriptability/Account+Scriptability.swift index c2cc7667a..7d6f7428b 100644 --- a/Mac/Scriptability/Account+Scriptability.swift +++ b/Mac/Scriptability/Account+Scriptability.swift @@ -73,7 +73,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta account.removeFolder(scriptableFolder.folder) { result in } } - } else if let scriptableFeed = element as? ScriptableFeed { + } else if let scriptableFeed = element as? ScriptableWebFeed { BatchUpdate.shared.perform { var container: Container? = nil if let scriptableFolder = scriptableFeed.container as? ScriptableFolder { @@ -94,23 +94,23 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta // MARK: --- Scriptable elements --- - @objc(feeds) - var feeds:NSArray { - return account.topLevelWebFeeds.map { ScriptableFeed($0, container:self) } as NSArray + @objc(webFeeds) + var webFeeds:NSArray { + return account.topLevelWebFeeds.map { ScriptableWebFeed($0, container:self) } as NSArray } - @objc(valueInFeedsWithUniqueID:) - func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? { + @objc(valueInWebFeedsWithUniqueID:) + func valueInWebFeeds(withUniqueID id:String) -> ScriptableWebFeed? { let feeds = Array(account.topLevelWebFeeds) guard let feed = feeds.first(where:{$0.webFeedID == id}) else { return nil } - return ScriptableFeed(feed, container:self) + return ScriptableWebFeed(feed, container:self) } - @objc(valueInFeedsWithName:) - func valueInFeeds(withName name:String) -> ScriptableFeed? { + @objc(valueInWebFeedsWithName:) + func valueInWebFeeds(withName name:String) -> ScriptableWebFeed? { let feeds = Array(account.topLevelWebFeeds) guard let feed = feeds.first(where:{$0.name == name}) else { return nil } - return ScriptableFeed(feed, container:self) + return ScriptableWebFeed(feed, container:self) } @objc(folders) @@ -131,21 +131,21 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta // MARK: --- Scriptable properties --- - @objc(allFeeds) - var allFeeds: NSArray { - var feeds = [ScriptableFeed]() - for feed in account.topLevelWebFeeds { - feeds.append(ScriptableFeed(feed, container: self)) + @objc(allWebFeeds) + var allWebFeeds: NSArray { + var webFeeds = [ScriptableWebFeed]() + for webFeed in account.topLevelWebFeeds { + webFeeds.append(ScriptableWebFeed(webFeed, container: self)) } if let folders = account.folders { for folder in folders { let scriptableFolder = ScriptableFolder(folder, container: self) - for feed in folder.topLevelWebFeeds { - feeds.append(ScriptableFeed(feed, container: scriptableFolder)) + for webFeed in folder.topLevelWebFeeds { + webFeeds.append(ScriptableWebFeed(webFeed, container: scriptableFolder)) } } } - return feeds as NSArray + return webFeeds as NSArray } @objc(opmlRepresentation) diff --git a/Mac/Scriptability/AppDelegate+Scriptability.swift b/Mac/Scriptability/AppDelegate+Scriptability.swift index 13e1e5e31..84bed12b1 100644 --- a/Mac/Scriptability/AppDelegate+Scriptability.swift +++ b/Mac/Scriptability/AppDelegate+Scriptability.swift @@ -59,8 +59,8 @@ extension AppDelegate : AppDelegateAppleEvents { class NetNewsWireCreateElementCommand : NSCreateCommand { override func performDefaultImplementation() -> Any? { let classDescription = self.createClassDescription - if (classDescription.className == "feed") { - return ScriptableFeed.handleCreateElement(command:self) + if (classDescription.className == "webFeed") { + return ScriptableWebFeed.handleCreateElement(command:self) } else if (classDescription.className == "folder") { return ScriptableFolder.handleCreateElement(command:self) } diff --git a/Mac/Scriptability/Folder+Scriptability.swift b/Mac/Scriptability/Folder+Scriptability.swift index 406f04490..2229ce3a7 100644 --- a/Mac/Scriptability/Folder+Scriptability.swift +++ b/Mac/Scriptability/Folder+Scriptability.swift @@ -51,7 +51,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai } func deleteElement(_ element:ScriptingObject) { - if let scriptableFeed = element as? ScriptableFeed { + if let scriptableFeed = element as? ScriptableWebFeed { BatchUpdate.shared.perform { folder.account?.removeWebFeed(scriptableFeed.webFeed, from: folder) { result in } } @@ -95,10 +95,10 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai // MARK: --- Scriptable elements --- - @objc(feeds) - var feeds:NSArray { + @objc(webFeeds) + var webFeeds:NSArray { let feeds = Array(folder.topLevelWebFeeds) - return feeds.map { ScriptableFeed($0, container:self) } as NSArray + return feeds.map { ScriptableWebFeed($0, container:self) } as NSArray } // MARK: --- Scriptable properties --- diff --git a/Mac/Scriptability/NSApplication+Scriptability.swift b/Mac/Scriptability/NSApplication+Scriptability.swift index 90f9aaffe..b99095259 100644 --- a/Mac/Scriptability/NSApplication+Scriptability.swift +++ b/Mac/Scriptability/NSApplication+Scriptability.swift @@ -31,7 +31,7 @@ extension NSApplication : ScriptingObjectContainer { var scriptableArticle: ScriptableArticle? if let currentArticle = appDelegate.scriptingCurrentArticle { if let feed = currentArticle.webFeed { - let scriptableFeed = ScriptableFeed(feed, container:self) + let scriptableFeed = ScriptableWebFeed(feed, container:self) scriptableArticle = ScriptableArticle(currentArticle, container:scriptableFeed) } } @@ -43,7 +43,7 @@ extension NSApplication : ScriptingObjectContainer { let articles = appDelegate.scriptingSelectedArticles let scriptableArticles:[ScriptableArticle] = articles.compactMap { article in if let feed = article.webFeed { - let scriptableFeed = ScriptableFeed(feed, container:self) + let scriptableFeed = ScriptableWebFeed(feed, container:self) return ScriptableArticle(article, container:scriptableFeed) } else { return nil @@ -73,7 +73,7 @@ extension NSApplication : ScriptingObjectContainer { for 'articles of feed "The Shape of Everything" of account "On My Mac"' */ - func allFeeds() -> [WebFeed] { + func allWebFeeds() -> [WebFeed] { let accounts = AccountManager.shared.activeAccounts let emptyFeeds:[WebFeed] = [] return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [WebFeed] in @@ -82,17 +82,17 @@ extension NSApplication : ScriptingObjectContainer { } } - @objc(feeds) - func feeds() -> NSArray { - let feeds = self.allFeeds() - return feeds.map { ScriptableFeed($0, container:self) } as NSArray + @objc(webFeeds) + func webFeeds() -> NSArray { + let webFeeds = self.allWebFeeds() + return webFeeds.map { ScriptableWebFeed($0, container:self) } as NSArray } - @objc(valueInFeedsWithUniqueID:) - func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? { - let feeds = self.allFeeds() - guard let feed = feeds.first(where:{$0.webFeedID == id}) else { return nil } - return ScriptableFeed(feed, container:self) + @objc(valueInWebFeedsWithUniqueID:) + func valueInWebFeeds(withUniqueID id:String) -> ScriptableWebFeed? { + let webFeeds = self.allWebFeeds() + guard let webFeed = webFeeds.first(where:{$0.webFeedID == id}) else { return nil } + return ScriptableWebFeed(webFeed, container:self) } } diff --git a/Mac/Scriptability/Feed+Scriptability.swift b/Mac/Scriptability/WebFeed+Scriptability.swift similarity index 93% rename from Mac/Scriptability/Feed+Scriptability.swift rename to Mac/Scriptability/WebFeed+Scriptability.swift index caed94af9..47f701901 100644 --- a/Mac/Scriptability/Feed+Scriptability.swift +++ b/Mac/Scriptability/WebFeed+Scriptability.swift @@ -11,14 +11,14 @@ import RSParser import Account import Articles -@objc(ScriptableFeed) -class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer { +@objc(ScriptableWebFeed) +class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer { let webFeed:WebFeed let container:ScriptingObjectContainer - init (_ feed:WebFeed, container:ScriptingObjectContainer) { - self.webFeed = feed + init (_ webFeed:WebFeed, container:ScriptingObjectContainer) { + self.webFeed = webFeed self.container = container } @@ -36,7 +36,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine // MARK: --- ScriptingObject protocol --- var scriptingKey: String { - return "feeds" + return "webFeeds" } // MARK: --- UniqueIdScriptingObject protocol --- @@ -71,13 +71,13 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine return url } - class func scriptableFeed(_ feed:WebFeed, account:Account, folder:Folder?) -> ScriptableFeed { + class func scriptableFeed(_ feed:WebFeed, account:Account, folder:Folder?) -> ScriptableWebFeed { let scriptableAccount = ScriptableAccount(account) if let folder = folder { let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount) - return ScriptableFeed(feed, container:scriptableFolder) + return ScriptableWebFeed(feed, container:scriptableFolder) } else { - return ScriptableFeed(feed, container:scriptableAccount) + return ScriptableWebFeed(feed, container:scriptableAccount) } } diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 8bac9c20b..e5f714d63 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -384,7 +384,7 @@ 65ED4037235DEF6C0081F399 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 65ED4038235DEF6C0081F399 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 65ED4039235DEF6C0081F399 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; - 65ED403A235DEF6C0081F399 /* Feed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */; }; + 65ED403A235DEF6C0081F399 /* WebFeed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */; }; 65ED403B235DEF6C0081F399 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; 65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; }; 65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97741ED9EC04007D329B /* TimelineTableCellView.swift */; }; @@ -622,7 +622,7 @@ D5E4CC54202C1361009B4FFC /* AppDelegate+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */; }; D5E4CC64202C1AC1009B4FFC /* MainWindowController+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */; }; D5F4EDB5200744A700B9E363 /* ScriptingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */; }; - D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */; }; + D5F4EDB720074D6500B9E363 /* WebFeed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */; }; D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; }; DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD82AB09231003F6002269DF /* SharingTests.swift */; }; FF3ABF13232599810074C542 /* ArticleSorterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF09232599450074C542 /* ArticleSorterTests.swift */; }; @@ -1549,7 +1549,7 @@ D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Scriptability.swift"; sourceTree = ""; }; D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainWindowController+Scriptability.swift"; sourceTree = ""; }; D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptingObject.swift; sourceTree = ""; }; - D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feed+Scriptability.swift"; sourceTree = ""; }; + D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebFeed+Scriptability.swift"; sourceTree = ""; }; D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Folder+Scriptability.swift"; sourceTree = ""; }; DD82AB09231003F6002269DF /* SharingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharingTests.swift; sourceTree = ""; }; FF3ABF09232599450074C542 /* ArticleSorterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorterTests.swift; sourceTree = ""; }; @@ -2689,7 +2689,7 @@ D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */, D553737C20186C1F006D8857 /* Article+Scriptability.swift */, D5A2678B20130ECF00A8D3C0 /* Author+Scriptability.swift */, - D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */, + D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */, D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */, D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */, D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */, @@ -3848,7 +3848,7 @@ 65ED4037235DEF6C0081F399 /* FolderTreeControllerDelegate.swift in Sources */, 65ED4038235DEF6C0081F399 /* RSImage-Extensions.swift in Sources */, 65ED4039235DEF6C0081F399 /* SingleFaviconDownloader.swift in Sources */, - 65ED403A235DEF6C0081F399 /* Feed+Scriptability.swift in Sources */, + 65ED403A235DEF6C0081F399 /* WebFeed+Scriptability.swift in Sources */, 65ED403B235DEF6C0081F399 /* AuthorAvatarDownloader.swift in Sources */, 65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */, 65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */, @@ -4137,7 +4137,7 @@ 849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */, 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */, 845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */, - D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */, + D5F4EDB720074D6500B9E363 /* WebFeed+Scriptability.swift in Sources */, 84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */, 84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */, 849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */, diff --git a/Tests/NetNewsWireTests/ArticleSorterTests.swift b/Tests/NetNewsWireTests/ArticleSorterTests.swift index 1991e88ff..131b7b676 100644 --- a/Tests/NetNewsWireTests/ArticleSorterTests.swift +++ b/Tests/NetNewsWireTests/ArticleSorterTests.swift @@ -19,10 +19,10 @@ class ArticleSorterTests: XCTestCase { func testSortByDateAscending() { let now = Date() - let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableFeedID: "4") - let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableFeedID: "6") - let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableFeedID: "6") - let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableFeedID: "5") + let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableWebFeedID: "4") + let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableWebFeedID: "6") + let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableWebFeedID: "6") + let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableWebFeedID: "5") let articles = [article1, article2, article3, article4] let sortedArticles = ArticleSorter.sortedByDate(articles: articles, @@ -40,11 +40,11 @@ class ArticleSorterTests: XCTestCase { let now = Date() // Articles with the same date should end up being sorted by their article ID - let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1") - let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2") - let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "3") - let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableFeedID: "4") - let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableFeedID: "5") + let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "1") + let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "2") + let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "3") + let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableWebFeedID: "4") + let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableWebFeedID: "5") let articles = [article1, article2, article3, article4, article5] let sortedArticles = ArticleSorter.sortedByDate(articles: articles, @@ -62,15 +62,15 @@ class ArticleSorterTests: XCTestCase { func testSortByDateAscendingWithGroupByFeed() { let now = Date() - let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableFeedID: "1") - let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2") - let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "2") - let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableFeedID: "3") - let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "3") - let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableFeedID: "2") - let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1") - let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "0") - let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "0") + let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableWebFeedID: "1") + let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "2") + let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "2") + let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableWebFeedID: "3") + let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "3") + let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableWebFeedID: "2") + let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "1") + let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "0") + let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "0") let articles = [article1, article2, article3, article4, article5, article6, article7, article8, article9] let sortedArticles = ArticleSorter.sortedByDate(articles: articles, sortDirection: .orderedAscending, groupByFeed: true) @@ -97,10 +97,10 @@ class ArticleSorterTests: XCTestCase { func testSortByDateDescending() { let now = Date() - let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableFeedID: "4") - let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableFeedID: "6") - let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableFeedID: "6") - let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableFeedID: "5") + let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableWebFeedID: "4") + let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableWebFeedID: "6") + let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableWebFeedID: "6") + let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableWebFeedID: "5") let articles = [article1, article2, article3, article4] let sortedArticles = ArticleSorter.sortedByDate(articles: articles, @@ -118,11 +118,11 @@ class ArticleSorterTests: XCTestCase { let now = Date() // Articles with the same date should end up being sorted by their article ID - let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1") - let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2") - let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "3") - let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableFeedID: "4") - let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableFeedID: "5") + let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "1") + let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "2") + let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "3") + let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableWebFeedID: "4") + let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableWebFeedID: "5") let articles = [article1, article2, article3, article4, article5] let sortedArticles = ArticleSorter.sortedByDate(articles: articles, @@ -140,15 +140,15 @@ class ArticleSorterTests: XCTestCase { func testSortByDateDescendingWithGroupByFeed() { let now = Date() - let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableFeedID: "1") - let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2") - let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "2") - let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableFeedID: "3") - let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "3") - let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableFeedID: "2") - let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1") - let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "0") - let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "0") + let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableWebFeedID: "1") + let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "2") + let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "2") + let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableWebFeedID: "3") + let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "3") + let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableWebFeedID: "2") + let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "1") + let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "0") + let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "0") let articles = [article1, article2, article3, article4, article5, article6, article7, article8, article9] let sortedArticles = ArticleSorter.sortedByDate(articles: articles, sortDirection: .orderedDescending, groupByFeed: true) @@ -175,11 +175,11 @@ class ArticleSorterTests: XCTestCase { func testGroupByFeedWithCaseInsensitiveFeedNames() { let now = Date() - let article1 = TestArticle(sortableName: "phil's feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1") - let article2 = TestArticle(sortableName: "PhIl's FEed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1") - let article3 = TestArticle(sortableName: "APPLE's feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "2") - let article4 = TestArticle(sortableName: "PHIL'S FEED", sortableDate: now, sortableArticleID: "4", sortableFeedID: "1") - let article5 = TestArticle(sortableName: "apple's feed", sortableDate: now, sortableArticleID: "5", sortableFeedID: "2") + let article1 = TestArticle(sortableName: "phil's feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "1") + let article2 = TestArticle(sortableName: "PhIl's FEed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "1") + let article3 = TestArticle(sortableName: "APPLE's feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "2") + let article4 = TestArticle(sortableName: "PHIL'S FEED", sortableDate: now, sortableArticleID: "4", sortableWebFeedID: "1") + let article5 = TestArticle(sortableName: "apple's feed", sortableDate: now, sortableArticleID: "5", sortableWebFeedID: "2") let articles = [article1, article2, article3, article4, article5] let sortedArticles = ArticleSorter.sortedByDate(articles: articles, @@ -201,11 +201,11 @@ class ArticleSorterTests: XCTestCase { let now = Date() // Articles with the same feed name should be sorted by feed ID - let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2") - let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2") - let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "1") - let article4 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "4", sortableFeedID: "2") - let article5 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "5", sortableFeedID: "1") + let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "2") + let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "2") + let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "1") + let article4 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "4", sortableWebFeedID: "2") + let article5 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "5", sortableWebFeedID: "1") let articles = [article1, article2, article3, article4, article5] let sortedArticles = ArticleSorter.sortedByDate(articles: articles, @@ -226,7 +226,7 @@ private struct TestArticle: SortableArticle, Equatable { let sortableName: String let sortableDate: Date let sortableArticleID: String - let sortableFeedID: String + let sortableWebFeedID: String } private extension Array where Element == TestArticle { diff --git a/Tests/NetNewsWireTests/ScriptingTests/scripts/testFeedExists.applescript b/Tests/NetNewsWireTests/ScriptingTests/scripts/testFeedExists.applescript index 098c1ff61..8eafaad24 100644 --- a/Tests/NetNewsWireTests/ScriptingTests/scripts/testFeedExists.applescript +++ b/Tests/NetNewsWireTests/ScriptingTests/scripts/testFeedExists.applescript @@ -1,7 +1,7 @@ -- this script just tests that no error was generated from the script try tell application "NetNewsWire" - exists feed 1 of account 1 + exists webFeed 1 of account 1 end tell on error message return {test_result:false, script_result:message} diff --git a/Tests/NetNewsWireTests/ScriptingTests/scripts/testFeedOPML.applescript b/Tests/NetNewsWireTests/ScriptingTests/scripts/testFeedOPML.applescript index 5d28a8604..dc4b422bb 100644 --- a/Tests/NetNewsWireTests/ScriptingTests/scripts/testFeedOPML.applescript +++ b/Tests/NetNewsWireTests/ScriptingTests/scripts/testFeedOPML.applescript @@ -1,7 +1,7 @@ -- this script just tests that no error was generated from the script try tell application "NetNewsWire" - opml representation of feed 1 of account 1 + opml representation of webFeed 1 of account 1 end tell on error message return {test_result:false, script_result:message} diff --git a/Tests/NetNewsWireTests/ScriptingTests/scripts/testNameAndUrlOfEveryFeed.applescript b/Tests/NetNewsWireTests/ScriptingTests/scripts/testNameAndUrlOfEveryFeed.applescript index 86268914b..7e21f3991 100644 --- a/Tests/NetNewsWireTests/ScriptingTests/scripts/testNameAndUrlOfEveryFeed.applescript +++ b/Tests/NetNewsWireTests/ScriptingTests/scripts/testNameAndUrlOfEveryFeed.applescript @@ -1,7 +1,7 @@ -- this script just tests that no error was generated from the script try tell application "NetNewsWire" - {name, url} of every feed of every account + {name, url} of every webFeed of every account end tell on error message return {test_result:false, script_result:message} diff --git a/Tests/NetNewsWireTests/ScriptingTests/scripts/testNameOfAuthors.applescript b/Tests/NetNewsWireTests/ScriptingTests/scripts/testNameOfAuthors.applescript index db15bab30..d88df5b9c 100644 --- a/Tests/NetNewsWireTests/ScriptingTests/scripts/testNameOfAuthors.applescript +++ b/Tests/NetNewsWireTests/ScriptingTests/scripts/testNameOfAuthors.applescript @@ -2,7 +2,7 @@ -- and that the returned list is greater than 0 try tell application "NetNewsWire" - set namesResult to name of every author of every feed of every account + set namesResult to name of every author of every webFeed of every account end tell set test_result to ((count items of namesResult) > 0) on error message diff --git a/Tests/NetNewsWireTests/SharingTests.swift b/Tests/NetNewsWireTests/SharingTests.swift index 4ce4455f9..8e8d801e4 100644 --- a/Tests/NetNewsWireTests/SharingTests.swift +++ b/Tests/NetNewsWireTests/SharingTests.swift @@ -39,24 +39,7 @@ class SharingTests: XCTestCase { private func article(titled title: String) -> Article { let articleId = randomId() - return Article( - accountID: randomId(), - articleID: articleId, - feedID: randomId(), - uniqueID: randomId(), - title: title, - contentHTML: nil, - contentText: nil, - url: nil, - externalURL: nil, - summary: nil, - imageURL: nil, - bannerImageURL: nil, - datePublished: nil, - dateModified: nil, - authors: nil, - attachments: nil, - status: ArticleStatus(articleID: articleId, read: true, dateArrived: Date()) + return Article(accountID: randomId(), articleID: articleId, webFeedID: randomId(), uniqueID: randomId(), title: title, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: ArticleStatus(articleID: articleId, read: true, dateArrived: Date()) ) } From 16da609fa97e45fabdd6f8bbf03d34574fc8f5af Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 15 Nov 2019 18:26:52 -0600 Subject: [PATCH 206/237] Restore Smart Feed or Folder path if possible when restoring an Article. Issue #1241 --- Frameworks/Articles/Article.swift | 6 +++ iOS/SceneCoordinator.swift | 62 ++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/Frameworks/Articles/Article.swift b/Frameworks/Articles/Article.swift index 8e11b72fd..1cdaa3b2d 100644 --- a/Frameworks/Articles/Article.swift +++ b/Frameworks/Articles/Article.swift @@ -78,6 +78,11 @@ public extension Set where Element == Article { let articles = self.filter { !$0.status.read } return Set(articles) } + + func contains(accountID: String, articleID: String) -> Bool { + return contains(where: { $0.accountID == accountID && $0.articleID == articleID}) + } + } public extension Array where Element == Article { @@ -85,4 +90,5 @@ public extension Array where Element == Article { func articleIDs() -> [String] { return map { $0.articleID } } + } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 21de1824d..77db16270 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -1656,8 +1656,9 @@ private extension SceneCoordinator { } func handleReadArticle(_ userInfo: [AnyHashable : Any]?) { - guard let userInfo = userInfo, - let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any], + guard let userInfo = userInfo else { return } + + guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any], let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String, let accountName = articlePathUserInfo[ArticlePathKey.accountName] as? String, let webFeedID = articlePathUserInfo[ArticlePathKey.webFeedID] as? String, @@ -1665,17 +1666,62 @@ private extension SceneCoordinator { return } + if restoreFeed(userInfo, accountID: accountID, articleID: articleID) { + return + } + guard let accountNode = findAccountNode(accountID: accountID, accountName: accountName), let feedNode = findWebFeedNode(webFeedID: webFeedID, beginningAt: accountNode) else { return } discloseFeed(feedNode.representedObject as! WebFeed, animated: false) { - if let article = self.articles.first(where: { $0.articleID == articleID }) { - self.selectArticle(article) - } + self.selectArticleInCurrentFeed(articleID) } } + func restoreFeed(_ userInfo: [AnyHashable : Any], accountID: String, articleID: String) -> Bool { + guard let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : Any], + let articleFetcherType = FeedIdentifier(userInfo: feedIdentifierUserInfo) else { + return false + } + + switch articleFetcherType { + + case .smartFeed(let identifier): + guard let smartFeed = SmartFeedsController.shared.find(by: identifier) else { return false } + if smartFeed.fetchArticles().contains(accountID: accountID, articleID: articleID) { + if let indexPath = indexPathFor(smartFeed) { + selectFeed(indexPath, animated: false) + selectArticleInCurrentFeed(articleID) + return true + } + } + + case .script: + return false + + case .folder(let accountID, let folderName): + guard let accountNode = findAccountNode(accountID: accountID), + let folderNode = findFolderNode(folderName: folderName, beginningAt: accountNode), + let folderFeed = folderNode.representedObject as? Feed else { + return false + } + if folderFeed.fetchArticles().contains(accountID: accountID, articleID: articleID) { + if let indexPath = indexPathFor(folderNode) { + selectFeed(indexPath, animated: false) + selectArticleInCurrentFeed(articleID) + return true + } + } + + case .webFeed: + return false + + } + + return false + } + func findAccountNode(accountID: String, accountName: String? = nil) -> Node? { if let node = treeController.rootNode.descendantNode(where: { ($0.representedObject as? Account)?.accountID == accountID }) { return node @@ -1702,4 +1748,10 @@ private extension SceneCoordinator { return nil } + func selectArticleInCurrentFeed(_ articleID: String) { + if let article = self.articles.first(where: { $0.articleID == articleID }) { + self.selectArticle(article) + } + } + } From a8927e250f9bdbd4d05fbdd01bd62095656f8932 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 01:34:43 -0600 Subject: [PATCH 207/237] Fix console constraint warnings. --- iOS/Account/Account.storyboard | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/iOS/Account/Account.storyboard b/iOS/Account/Account.storyboard index 02da09c44..fb90e6288 100644 --- a/iOS/Account/Account.storyboard +++ b/iOS/Account/Account.storyboard @@ -95,10 +95,9 @@ + - - @@ -143,7 +142,7 @@ - + @@ -212,9 +211,8 @@ - - + @@ -257,7 +255,7 @@ - + From 91a3e4fd7378eba1f68bbe07282bc32b8421d64b Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 12:02:58 -0600 Subject: [PATCH 208/237] Change Add Web Feed folder selection to be a separate controlling instead of the picker wheel. Issue #1270 --- Frameworks/Account/Account.swift | 8 + Mac/AppAssets.swift | 16 ++ NetNewsWire.xcodeproj/project.pbxproj | 22 +- Shared/Data/SmallIconProvider.swift | 9 + iOS/Add/Add.storyboard | 232 ++++++++++++------ iOS/Add/AddContainerViewController.swift | 2 + iOS/Add/AddWebFeedFolderTableViewCell.swift | 16 ++ iOS/Add/AddWebFeedFolderViewController.swift | 78 ++++++ iOS/Add/AddWebFeedViewController.swift | 75 +++--- iOS/AppDefaults.swift | 28 +++ .../TimelineCustomizerViewController.swift | 4 +- 11 files changed, 367 insertions(+), 123 deletions(-) create mode 100644 iOS/Add/AddWebFeedFolderTableViewCell.swift create mode 100644 iOS/Add/AddWebFeedFolderViewController.swift diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 684291fc5..e9d36d37f 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -128,6 +128,14 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, public var topLevelWebFeeds = Set() public var folders: Set? = Set() + + public var sortedFolders: [Folder]? { + if let folders = folders { + return Array(folders).sorted(by: { $0.nameForDisplay > $1.nameForDisplay }) + } + return nil + } + private var webFeedDictionaryNeedsUpdate = true private var _idToWebFeedDictionary = [String: WebFeed]() var idToWebFeedDictionary: [String: WebFeed] { diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index 644ce65ea..cf157c2cc 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -8,6 +8,7 @@ import AppKit import RSCore +import Account extension NSImage.Name { static let star = NSImage.Name("star") @@ -116,4 +117,19 @@ struct AppAssets { return RSImage(named: "swipeMarkUnstarred")! }() + static func image(for accountType: AccountType) -> NSImage? { + switch accountType { + case .onMyMac: + return AppAssets.accountLocal + case .feedbin: + return AppAssets.accountFeedbin + case .feedly: + return AppAssets.accountFeedly + case .freshRSS: + return AppAssets.accountFreshRSS + default: + return nil + } + } + } diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index e5f714d63..a95a35bf9 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -101,7 +101,6 @@ 516AE9DF2372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; 516AE9E02372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; 51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; }; - 5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; }; 517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 517630052336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */; }; @@ -169,7 +168,6 @@ 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */; }; 51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452752265091600C03939 /* MasterTimelineDefaultCellLayout.swift */; }; 51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4527E2265092C00C03939 /* ArticleViewController.swift */; }; - 51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; }; 51C452862265093600C03939 /* Add.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51C452822265093600C03939 /* Add.storyboard */; }; 51C452882265093600C03939 /* AddWebFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452842265093600C03939 /* AddWebFeedViewController.swift */; }; 51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4528B2265095F00C03939 /* AddFolderViewController.swift */; }; @@ -212,6 +210,10 @@ 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; 51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; }; 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB3C229AB08300645299 /* ErrorHandler.swift */; }; + 51E43962238037C400015C31 /* AddWebFeedFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */; }; + 51E4397D23805A6D00015C31 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4397C23805A6C00015C31 /* FlattenedAccountFolderPickerData.swift */; }; + 51E4397E23805A6D00015C31 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4397C23805A6C00015C31 /* FlattenedAccountFolderPickerData.swift */; }; + 51E4398023805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4397F23805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift */; }; 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */; }; @@ -1306,7 +1308,6 @@ 51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterUnreadIndicatorView.swift; sourceTree = ""; }; 51C452752265091600C03939 /* MasterTimelineDefaultCellLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterTimelineDefaultCellLayout.swift; sourceTree = ""; }; 51C4527E2265092C00C03939 /* ArticleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleViewController.swift; sourceTree = ""; }; - 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlattenedAccountFolderPickerData.swift; sourceTree = ""; }; 51C452822265093600C03939 /* Add.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Add.storyboard; sourceTree = ""; }; 51C452842265093600C03939 /* AddWebFeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddWebFeedViewController.swift; sourceTree = ""; }; 51C4528B2265095F00C03939 /* AddFolderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderViewController.swift; sourceTree = ""; }; @@ -1319,6 +1320,9 @@ 51D87EE02311D34700E63F03 /* ActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityType.swift; sourceTree = ""; }; 51E3EB32229AB02C00645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 51E3EB3C229AB08300645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; + 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedFolderViewController.swift; sourceTree = ""; }; + 51E4397C23805A6C00015C31 /* FlattenedAccountFolderPickerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlattenedAccountFolderPickerData.swift; sourceTree = ""; }; + 51E4397F23805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedFolderTableViewCell.swift; sourceTree = ""; }; 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleStatusSyncTimer.swift; sourceTree = ""; }; 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicButton.swift; sourceTree = ""; }; 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FolderTreeMenu.swift; path = AddFeed/FolderTreeMenu.swift; sourceTree = ""; }; @@ -1706,7 +1710,7 @@ 512E08DD22687FA000BDCFDD /* Tree */ = { isa = PBXGroup; children = ( - 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */, + 51E4397C23805A6C00015C31 /* FlattenedAccountFolderPickerData.swift */, 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */, 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */, ); @@ -1925,8 +1929,10 @@ 51C452822265093600C03939 /* Add.storyboard */, 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */, 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */, - 51C452842265093600C03939 /* AddWebFeedViewController.swift */, 51C4528B2265095F00C03939 /* AddFolderViewController.swift */, + 51C452842265093600C03939 /* AddWebFeedViewController.swift */, + 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */, + 51E4397F23805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift */, ); path = Add; sourceTree = ""; @@ -3692,8 +3698,8 @@ 515D4FC123257A3200EE1167 /* FolderTreeControllerDelegate.swift in Sources */, 515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */, 513C5CE9232571C2003D4054 /* ShareViewController.swift in Sources */, - 5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */, 51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */, + 51E4397E23805A6D00015C31 /* FlattenedAccountFolderPickerData.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3876,6 +3882,7 @@ 512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */, 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */, 51EF0F79227716380050506E /* ColorHash.swift in Sources */, + 51E4397D23805A6D00015C31 /* FlattenedAccountFolderPickerData.swift in Sources */, 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */, 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */, 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */, @@ -3889,6 +3896,7 @@ 51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */, 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, 517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */, + 51E43962238037C400015C31 /* AddWebFeedFolderViewController.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, 51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */, 513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */, @@ -3906,7 +3914,6 @@ 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */, - 51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */, 51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */, 5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */, 84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */, @@ -3934,6 +3941,7 @@ 51C45258226508CF00C03939 /* AppAssets.swift in Sources */, 51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */, 51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */, + 51E4398023805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift in Sources */, 51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */, 51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */, 51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */, diff --git a/Shared/Data/SmallIconProvider.swift b/Shared/Data/SmallIconProvider.swift index 3fb85ec9d..a26b83fb6 100644 --- a/Shared/Data/SmallIconProvider.swift +++ b/Shared/Data/SmallIconProvider.swift @@ -16,6 +16,15 @@ protocol SmallIconProvider { var smallIcon: IconImage? { get } } +extension Account: SmallIconProvider { + var smallIcon: IconImage? { + if let image = AppAssets.image(for: type) { + return IconImage(image) + } + return nil + } +} + extension WebFeed: SmallIconProvider { var smallIcon: IconImage? { diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index 9e37ccd87..89934d793 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -7,26 +7,25 @@ - + - + - - + - + - + @@ -40,14 +39,14 @@ - + - + - + @@ -60,56 +59,32 @@ - - - - - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -121,7 +96,7 @@ - + @@ -130,9 +105,9 @@ + - - + @@ -141,6 +116,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -165,31 +228,43 @@ - - + + + + + + + + + + + + + + + + + + - + + + - - - - - - - - + - + - + + + + - - - + @@ -225,6 +300,7 @@ + @@ -236,22 +312,21 @@ - + - - + - + - + @@ -269,10 +344,10 @@ - + - + - + - + - + @@ -326,6 +401,7 @@ + @@ -334,7 +410,7 @@ - + diff --git a/iOS/Add/AddContainerViewController.swift b/iOS/Add/AddContainerViewController.swift index b8729be97..5a9245c33 100644 --- a/iOS/Add/AddContainerViewController.swift +++ b/iOS/Add/AddContainerViewController.swift @@ -28,6 +28,7 @@ class AddContainerViewController: UIViewController { @IBOutlet weak var cancelButton: UIBarButtonItem! @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView! @IBOutlet weak var addButton: UIBarButtonItem! + @IBOutlet weak var typeSelectorContainer: UIView! @IBOutlet weak var typeSelectorSegmentedControl: UISegmentedControl! @IBOutlet weak var containerView: UIView! @@ -43,6 +44,7 @@ class AddContainerViewController: UIViewController { activityIndicatorView.color = UIColor.label activityIndicatorView.isHidden = true + typeSelectorContainer.layer.cornerRadius = 10 typeSelectorSegmentedControl.selectedSegmentIndex = initialControllerType?.rawValue ?? 0 switch initialControllerType { case .feed: diff --git a/iOS/Add/AddWebFeedFolderTableViewCell.swift b/iOS/Add/AddWebFeedFolderTableViewCell.swift new file mode 100644 index 000000000..c8c849956 --- /dev/null +++ b/iOS/Add/AddWebFeedFolderTableViewCell.swift @@ -0,0 +1,16 @@ +// +// AddWebFeedFolderTableViewCell.swift +// NetNewsWire +// +// Created by Maurice Parker on 11/16/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class AddWebFeedFolderTableViewCell: UITableViewCell { + + @IBOutlet weak var icon: UIImageView! + @IBOutlet weak var label: UILabel! + +} diff --git a/iOS/Add/AddWebFeedFolderViewController.swift b/iOS/Add/AddWebFeedFolderViewController.swift new file mode 100644 index 000000000..cdf3b7978 --- /dev/null +++ b/iOS/Add/AddWebFeedFolderViewController.swift @@ -0,0 +1,78 @@ +// +// AddWebFeedLocationViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 11/16/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit +import RSCore +import Account + +protocol AddWebFeedFolderViewControllerDelegate { + func didSelect(container: Container) +} + +class AddWebFeedFolderViewController: UITableViewController { + + var delegate: AddWebFeedFolderViewControllerDelegate? + var initialContainer: Container? + + var containers = [Container]() + + override func viewDidLoad() { + super.viewDidLoad() + + for account in AccountManager.shared.sortedActiveAccounts { + containers.append(account) + if let sortedFolders = account.sortedFolders { + containers.append(contentsOf: sortedFolders) + } + } + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return containers.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let container = containers[indexPath.row] + let cell: AddWebFeedFolderTableViewCell = { + if container is Account { + return tableView.dequeueReusableCell(withIdentifier: "AccountCell", for: indexPath) as! AddWebFeedFolderTableViewCell + } else { + return tableView.dequeueReusableCell(withIdentifier: "FolderCell", for: indexPath) as! AddWebFeedFolderTableViewCell + } + }() + + if let smallIconProvider = container as? SmallIconProvider { + cell.icon?.image = smallIconProvider.smallIcon?.image + } + + if let displayNameProvider = container as? DisplayNameProvider { + cell.label?.text = displayNameProvider.nameForDisplay + } + + if let compContainer = initialContainer, container === compContainer { + cell.accessoryType = .checkmark + } + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let cell = tableView.cellForRow(at: indexPath) + cell?.accessoryType = .checkmark + + delegate?.didSelect(container: containers[indexPath.row]) + navigationController?.popViewController(animated: true) + } + +} diff --git a/iOS/Add/AddWebFeedViewController.swift b/iOS/Add/AddWebFeedViewController.swift index 49774cc60..ec119fe42 100644 --- a/iOS/Add/AddWebFeedViewController.swift +++ b/iOS/Add/AddWebFeedViewController.swift @@ -16,20 +16,16 @@ class AddWebFeedViewController: UITableViewController, AddContainerViewControlle @IBOutlet private weak var urlTextField: UITextField! @IBOutlet private weak var nameTextField: UITextField! - @IBOutlet private weak var folderPickerView: UIPickerView! @IBOutlet private weak var folderLabel: UILabel! - private lazy var pickerData: FlattenedAccountFolderPickerData = FlattenedAccountFolderPickerData() - private var shouldDisplayPicker: Bool { - return pickerData.containerNames.count > 1 - } - private var userCancelled = false weak var delegate: AddContainerViewControllerChildDelegate? var initialFeed: String? var initialFeedName: String? + var container: Container? + override func viewDidLoad() { super.viewDidLoad() @@ -51,15 +47,17 @@ class AddWebFeedViewController: UITableViewController, AddContainerViewControlle nameTextField.text = initialFeedName nameTextField.delegate = self - folderLabel.text = pickerData.containerNames.first - if shouldDisplayPicker { - folderPickerView.dataSource = self - folderPickerView.delegate = self + if let accountID = AppDefaults.addWebFeedAccountID, let account = AccountManager.shared.activeAccounts.first(where: { $0.accountID == accountID }) { + container = account + } else if let account = AccountManager.shared.sortedActiveAccounts.first { + container = account } else { - folderPickerView.isHidden = true + delegate?.readyToAdd(state: false) } + updateFolderLabel() + // I couldn't figure out the gap at the top of the UITableView, so I took a hammer to it. tableView.contentInset = UIEdgeInsets(top: -28, left: 0, bottom: 0, right: 0) @@ -82,7 +80,7 @@ class AddWebFeedViewController: UITableViewController, AddContainerViewControlle return } - let container = pickerData.containers[folderPickerView.selectedRow(inComponent: 0)] + guard let container = container else { return } var account: Account? if let containerAccount = container as? Account { @@ -123,38 +121,29 @@ class AddWebFeedViewController: UITableViewController, AddContainerViewControlle delegate?.readyToAdd(state: urlTextField.text?.rs_stringMayBeURL() ?? false) } - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let defaultNumberOfRows = super.tableView(tableView, numberOfRowsInSection: section) - if section == 1 && !shouldDisplayPicker { - return defaultNumberOfRows - 1 + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.row == 2 { + let folderViewController = UIStoryboard.add.instantiateController(ofType: AddWebFeedFolderViewController.self) + folderViewController.delegate = self + folderViewController.initialContainer = container + navigationController?.pushViewController(folderViewController, animated: true) } - - return defaultNumberOfRows } - } -extension AddWebFeedViewController: UIPickerViewDataSource, UIPickerViewDelegate { - - func numberOfComponents(in pickerView: UIPickerView) ->Int { - return 1 +// MARK: AddWebFeedFolderViewControllerDelegate + +extension AddWebFeedViewController: AddWebFeedFolderViewControllerDelegate { + func didSelect(container: Container) { + self.container = container + updateFolderLabel() + AppDefaults.addWebFeedAccountID = container.account?.accountID } - - func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return pickerData.containerNames.count - } - - func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - return pickerData.containerNames[row] - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - folderLabel.text = pickerData.containerNames[row] - } - } +// MARK: UITextFieldDelegate + extension AddWebFeedViewController: UITextFieldDelegate { func textFieldShouldReturn(_ textField: UITextField) -> Bool { @@ -163,3 +152,17 @@ extension AddWebFeedViewController: UITextFieldDelegate { } } + +// MARK: Private + +private extension AddWebFeedViewController { + func updateFolderLabel() { + if let containerName = (container as? DisplayNameProvider)?.nameForDisplay { + if container is Folder { + folderLabel.text = "\(container?.account?.nameForDisplay ?? "") / \(containerName)" + } else { + folderLabel.text = containerName + } + } + } +} diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index e2e576835..13599d85d 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -25,6 +25,8 @@ struct AppDefaults { static let timelineSortDirection = "timelineSortDirection" static let displayUndoAvailableTip = "displayUndoAvailableTip" static let lastRefresh = "lastRefresh" + static let addWebFeedAccountID = "addWebFeedAccountID" + static let addFolderAccountID = "addFolderAccountID" } static let isFirstRun: Bool = { @@ -34,6 +36,24 @@ struct AppDefaults { firstRunDate = Date() return true }() + + static var addWebFeedAccountID: String? { + get { + return string(for: Key.addWebFeedAccountID) + } + set { + setString(for: Key.addWebFeedAccountID, newValue) + } + } + + static var addFolderAccountID: String? { + get { + return string(for: Key.addFolderAccountID) + } + set { + setString(for: Key.addFolderAccountID, newValue) + } + } static var lastImageCacheFlushDate: Date? { get { @@ -122,6 +142,14 @@ private extension AppDefaults { } } + static func string(for key: String) -> String? { + return UserDefaults.standard.string(forKey: key) + } + + static func setString(for key: String, _ value: String?) { + UserDefaults.standard.set(value, forKey: key) + } + static func bool(for key: String) -> Bool { return AppDefaults.shared.bool(forKey: key) } diff --git a/iOS/Settings/TimelineCustomizerViewController.swift b/iOS/Settings/TimelineCustomizerViewController.swift index 07e642e4b..284b5785d 100644 --- a/iOS/Settings/TimelineCustomizerViewController.swift +++ b/iOS/Settings/TimelineCustomizerViewController.swift @@ -26,11 +26,11 @@ class TimelineCustomizerViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - iconSizeSliderContainerView.layer.cornerRadius = 12 + iconSizeSliderContainerView.layer.cornerRadius = 10 iconSizeSlider.value = Float(AppDefaults.timelineIconSize.rawValue) iconSizeSlider.addTickMarks() - numberOfLinesSliderContainerView.layer.cornerRadius = 12 + numberOfLinesSliderContainerView.layer.cornerRadius = 10 numberOfLinesSlider.value = Float(AppDefaults.timelineNumberOfLines) numberOfLinesSlider.addTickMarks() } From 5ce5175bc0f3b3d63046cc867e1b90262f45e96c Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 13:25:55 -0600 Subject: [PATCH 209/237] Change to not suggest root folder for Accounts that don't support it. Issue #1287 --- Frameworks/Account/Account.swift | 2 +- NetNewsWire.xcodeproj/project.pbxproj | 4 ++ iOS/Add/AddWebFeedDefaultContainer.swift | 51 ++++++++++++++++++++ iOS/Add/AddWebFeedFolderViewController.swift | 13 +++-- iOS/Add/AddWebFeedViewController.swift | 8 ++- iOS/AppDefaults.swift | 10 ++++ 6 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 iOS/Add/AddWebFeedDefaultContainer.swift diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index e9d36d37f..065d90135 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -131,7 +131,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, public var sortedFolders: [Folder]? { if let folders = folders { - return Array(folders).sorted(by: { $0.nameForDisplay > $1.nameForDisplay }) + return Array(folders).sorted(by: { $0.nameForDisplay < $1.nameForDisplay }) } return nil } diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index a95a35bf9..572db7b4e 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -128,6 +128,7 @@ 51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16993235E10D600EB091F /* SettingsViewController.swift */; }; 51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16995235E10D600EB091F /* AboutViewController.swift */; }; 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */; }; + 51A66685238075AE00CB272D /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; 51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; }; 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; }; 51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; }; @@ -1290,6 +1291,7 @@ 51A16993235E10D600EB091F /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = ""; }; + 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedDefaultContainer.swift; sourceTree = ""; }; 51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; 51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; @@ -1933,6 +1935,7 @@ 51C452842265093600C03939 /* AddWebFeedViewController.swift */, 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */, 51E4397F23805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift */, + 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */, ); path = Add; sourceTree = ""; @@ -3929,6 +3932,7 @@ 51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */, 51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */, 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */, + 51A66685238075AE00CB272D /* AddWebFeedDefaultContainer.swift in Sources */, 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */, 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */, 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */, diff --git a/iOS/Add/AddWebFeedDefaultContainer.swift b/iOS/Add/AddWebFeedDefaultContainer.swift new file mode 100644 index 000000000..8e52b17f8 --- /dev/null +++ b/iOS/Add/AddWebFeedDefaultContainer.swift @@ -0,0 +1,51 @@ +// +// AddWebFeedDefaultContainer.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 11/16/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import Account + +struct AddWebFeedDefaultContainer { + + static var defaultContainer: Container? { + + if let accountID = AppDefaults.addWebFeedAccountID, let account = AccountManager.shared.activeAccounts.first(where: { $0.accountID == accountID }) { + if let folderName = AppDefaults.addWebFeedFolderName, let folder = account.findFolder(withDisplayName: folderName) { + return folder + } else { + return substituteContainerIfNeeded(account: account) + } + } else if let account = AccountManager.shared.sortedActiveAccounts.first { + return substituteContainerIfNeeded(account: account) + } else { + return nil + } + + } + + static func storeDefaultContainer(_ container: Container) { + AppDefaults.addWebFeedAccountID = container.account?.accountID + if let folder = container as? Folder { + AppDefaults.addWebFeedFolderName = folder.nameForDisplay + } else { + AppDefaults.addWebFeedFolderName = nil + } + } + + private static func substituteContainerIfNeeded(account: Account) -> Container? { + if !account.behaviors.contains(.disallowFeedInRootFolder) { + return account + } else { + if let folder = account.sortedFolders?.first { + return folder + } else { + return nil + } + } + } + +} diff --git a/iOS/Add/AddWebFeedFolderViewController.swift b/iOS/Add/AddWebFeedFolderViewController.swift index cdf3b7978..b90c55472 100644 --- a/iOS/Add/AddWebFeedFolderViewController.swift +++ b/iOS/Add/AddWebFeedFolderViewController.swift @@ -68,11 +68,16 @@ class AddWebFeedFolderViewController: UITableViewController { } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let cell = tableView.cellForRow(at: indexPath) - cell?.accessoryType = .checkmark + let container = containers[indexPath.row] - delegate?.didSelect(container: containers[indexPath.row]) - navigationController?.popViewController(animated: true) + if let account = container as? Account, account.behaviors.contains(.disallowFeedInRootFolder) { + tableView.selectRow(at: nil, animated: false, scrollPosition: .none) + } else { + let cell = tableView.cellForRow(at: indexPath) + cell?.accessoryType = .checkmark + delegate?.didSelect(container: container) + navigationController?.popViewController(animated: true) + } } } diff --git a/iOS/Add/AddWebFeedViewController.swift b/iOS/Add/AddWebFeedViewController.swift index ec119fe42..c915c0c1c 100644 --- a/iOS/Add/AddWebFeedViewController.swift +++ b/iOS/Add/AddWebFeedViewController.swift @@ -48,10 +48,8 @@ class AddWebFeedViewController: UITableViewController, AddContainerViewControlle nameTextField.text = initialFeedName nameTextField.delegate = self - if let accountID = AppDefaults.addWebFeedAccountID, let account = AccountManager.shared.activeAccounts.first(where: { $0.accountID == accountID }) { - container = account - } else if let account = AccountManager.shared.sortedActiveAccounts.first { - container = account + if let defaultContainer = AddWebFeedDefaultContainer.defaultContainer { + container = defaultContainer } else { delegate?.readyToAdd(state: false) } @@ -138,7 +136,7 @@ extension AddWebFeedViewController: AddWebFeedFolderViewControllerDelegate { func didSelect(container: Container) { self.container = container updateFolderLabel() - AppDefaults.addWebFeedAccountID = container.account?.accountID + AddWebFeedDefaultContainer.storeDefaultContainer(container) } } diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index 13599d85d..341ecd7d5 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -26,6 +26,7 @@ struct AppDefaults { static let displayUndoAvailableTip = "displayUndoAvailableTip" static let lastRefresh = "lastRefresh" static let addWebFeedAccountID = "addWebFeedAccountID" + static let addWebFeedFolderName = "addWebFeedFolderName" static let addFolderAccountID = "addFolderAccountID" } @@ -46,6 +47,15 @@ struct AppDefaults { } } + static var addWebFeedFolderName: String? { + get { + return string(for: Key.addWebFeedFolderName) + } + set { + setString(for: Key.addWebFeedFolderName, newValue) + } + } + static var addFolderAccountID: String? { get { return string(for: Key.addFolderAccountID) From 75d00f46923e1e3dac2224176f547896a952908c Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 14:19:51 -0600 Subject: [PATCH 210/237] Add slide in/out transition for Add Feed / Add Folder. --- iOS/Add/Add.storyboard | 46 ++++++++++++++----- iOS/Add/AddContainerViewController.swift | 48 ++++++++++---------- iOS/Add/AddWebFeedFolderViewController.swift | 16 ++++++- iOS/Add/AddWebFeedViewController.swift | 6 ++- 4 files changed, 77 insertions(+), 39 deletions(-) diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index 89934d793..047e24941 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -17,7 +17,7 @@ - + @@ -38,7 +38,7 @@ - + @@ -59,21 +59,21 @@ - + - + - - - + + @@ -116,6 +116,22 @@ + + + + + + + + + + + + + + + + @@ -198,11 +214,17 @@ - + + + + + + + - + @@ -410,7 +432,7 @@ - + diff --git a/iOS/Add/AddContainerViewController.swift b/iOS/Add/AddContainerViewController.swift index 5a9245c33..38aeaf2be 100644 --- a/iOS/Add/AddContainerViewController.swift +++ b/iOS/Add/AddContainerViewController.swift @@ -113,7 +113,6 @@ private extension AddContainerViewController { navigationItem.title = NSLocalizedString("Add Web Feed", comment: "Add Web Feed") resetUI() - hideCurrentController() let addFeedController = UIStoryboard.add.instantiateController(ofType: AddWebFeedViewController.self) addFeedController.initialFeed = initialFeed @@ -131,7 +130,6 @@ private extension AddContainerViewController { navigationItem.title = NSLocalizedString("Add Folder", comment: "Add Folder") resetUI() - hideCurrentController() displayContentController(UIStoryboard.add.instantiateController(ofType: AddFolderViewController.self)) } @@ -141,30 +139,32 @@ private extension AddContainerViewController { } func displayContentController(_ controller: AddContainerViewControllerChild) { + if let currentViewController = currentViewController { + + let transition = CATransition() + transition.type = .push + transition.subtype = currentViewController is AddWebFeedViewController ? .fromRight : .fromLeft + containerView.layer.add(transition, forKey: "transition") + + containerView.addSubview(controller.view) + addChild(controller) + controller.didMove(toParent: self) + + currentViewController.willMove(toParent: nil) + currentViewController.view.removeFromSuperview() + currentViewController.removeFromParent() + + } else { + + containerView.addSubview(controller.view) + addChild(controller) + controller.didMove(toParent: self) + + } currentViewController = controller - controller.delegate = self - - addChild(controller) - - containerView.addSubview(controller.view) - controller.view.translatesAutoresizingMaskIntoConstraints = false - controller.view.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true - controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true - controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true - controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true - - controller.didMove(toParent: self) + currentViewController?.delegate = self } - - func hideCurrentController() { - guard let currentViewController = currentViewController else { - return - } - currentViewController.willMove(toParent: nil) - currentViewController.view.removeFromSuperview() - currentViewController.removeFromParent() - } - + } diff --git a/iOS/Add/AddWebFeedFolderViewController.swift b/iOS/Add/AddWebFeedFolderViewController.swift index b90c55472..8ed6d0227 100644 --- a/iOS/Add/AddWebFeedFolderViewController.swift +++ b/iOS/Add/AddWebFeedFolderViewController.swift @@ -76,8 +76,22 @@ class AddWebFeedFolderViewController: UITableViewController { let cell = tableView.cellForRow(at: indexPath) cell?.accessoryType = .checkmark delegate?.didSelect(container: container) - navigationController?.popViewController(animated: true) + dismiss() } } + // MARK: Actions + + @IBAction func cancel(_ sender: Any) { + dismiss() + } + +} + +private extension AddWebFeedFolderViewController { + + func dismiss() { + dismiss(animated: true) + } + } diff --git a/iOS/Add/AddWebFeedViewController.swift b/iOS/Add/AddWebFeedViewController.swift index c915c0c1c..465fdaee2 100644 --- a/iOS/Add/AddWebFeedViewController.swift +++ b/iOS/Add/AddWebFeedViewController.swift @@ -121,10 +121,12 @@ class AddWebFeedViewController: UITableViewController, AddContainerViewControlle override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.row == 2 { - let folderViewController = UIStoryboard.add.instantiateController(ofType: AddWebFeedFolderViewController.self) + let navController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddWebFeedFolderNavViewController") as! UINavigationController + navController.modalPresentationStyle = .currentContext + let folderViewController = navController.topViewController as! AddWebFeedFolderViewController folderViewController.delegate = self folderViewController.initialContainer = container - navigationController?.pushViewController(folderViewController, animated: true) + present(navController, animated: true) } } From e04f1ae7a14cd2c385b9c9cc1406a0927a540b2d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 14:28:37 -0600 Subject: [PATCH 211/237] Move Folder label to right side again. --- iOS/Add/Add.storyboard | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index 047e24941..002bd9e2f 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -66,14 +66,14 @@ - - - + + From 7ea838f793c57e954466c34cb8b105d51e065ecc Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 14:34:35 -0600 Subject: [PATCH 212/237] Added missing constraints needed for iPad. --- iOS/Add/AddContainerViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/Add/AddContainerViewController.swift b/iOS/Add/AddContainerViewController.swift index 38aeaf2be..38e3683d2 100644 --- a/iOS/Add/AddContainerViewController.swift +++ b/iOS/Add/AddContainerViewController.swift @@ -146,7 +146,7 @@ private extension AddContainerViewController { transition.subtype = currentViewController is AddWebFeedViewController ? .fromRight : .fromLeft containerView.layer.add(transition, forKey: "transition") - containerView.addSubview(controller.view) + containerView.addChildAndPin(controller.view) addChild(controller) controller.didMove(toParent: self) @@ -156,7 +156,7 @@ private extension AddContainerViewController { } else { - containerView.addSubview(controller.view) + containerView.addChildAndPin(controller.view) addChild(controller) controller.didMove(toParent: self) From 1b13c0e5896aa1523a471b56e9ef4984fb105ca3 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 15:16:42 -0600 Subject: [PATCH 213/237] Fix vibrant cell usage in new code --- iOS/Add/Add.storyboard | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index 002bd9e2f..66f95e687 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -3,6 +3,7 @@ + @@ -59,7 +60,7 @@ - + @@ -149,6 +150,7 @@ + @@ -183,6 +185,7 @@ + @@ -435,4 +438,9 @@ + + + + + From 901ac3b6aa980f94bf1469c3e0ae5409de53e206 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 15:28:57 -0600 Subject: [PATCH 214/237] Make Add Folder remember last selected Account. Issue #1303 --- iOS/Add/AddFolderViewController.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/iOS/Add/AddFolderViewController.swift b/iOS/Add/AddFolderViewController.swift index be70a3d9d..3d2dc0047 100644 --- a/iOS/Add/AddFolderViewController.swift +++ b/iOS/Add/AddFolderViewController.swift @@ -37,6 +37,11 @@ class AddFolderViewController: UITableViewController, AddContainerViewController if shouldDisplayPicker { accountPickerView.dataSource = self accountPickerView.delegate = self + + if let index = accounts.firstIndex(where: { $0.accountID == AppDefaults.addFolderAccountID }) { + accountPickerView.selectRow(index, inComponent: 0, animated: false) + } + } else { accountPickerView.isHidden = true } @@ -96,6 +101,7 @@ extension AddFolderViewController: UIPickerViewDataSource, UIPickerViewDelegate func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { accountLabel.text = (accounts[row] as DisplayNameProvider).nameForDisplay + AppDefaults.addFolderAccountID = accounts[row].accountID } } From e74e6cb875ef3cb79c4cc6999dffd0d31e7d5be9 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 15:47:12 -0600 Subject: [PATCH 215/237] Add Account scenes should have account graphic in header. Issue #1305 --- NetNewsWire.xcodeproj/project.pbxproj | 10 +++++----- iOS/Account/FeedbinAccountViewController.swift | 16 ++++++++++++++++ iOS/Account/LocalAccountViewController.swift | 16 ++++++++++++++++ .../AccountInspectorViewController.swift | 4 ++-- .../ImageHeaderView.swift} | 4 ++-- 5 files changed, 41 insertions(+), 9 deletions(-) rename iOS/{Inspector/InspectorImageHeaderView.swift => UIKit Extensions/ImageHeaderView.swift} (88%) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 572db7b4e..ef5834836 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -31,7 +31,7 @@ 5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */; }; 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; }; 5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; - 512AF9C2236ED52C0066F8BE /* InspectorImageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512AF9C1236ED52C0066F8BE /* InspectorImageHeaderView.swift */; }; + 512AF9C2236ED52C0066F8BE /* ImageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512AF9C1236ED52C0066F8BE /* ImageHeaderView.swift */; }; 512AF9DD236F05230066F8BE /* InteractiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512AF9DC236F05230066F8BE /* InteractiveLabel.swift */; }; 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; }; @@ -1222,7 +1222,7 @@ 5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneCoordinator.swift; sourceTree = ""; }; 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailKeyboardDelegate.swift; sourceTree = ""; }; 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DetailKeyboardShortcuts.plist; sourceTree = ""; }; - 512AF9C1236ED52C0066F8BE /* InspectorImageHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorImageHeaderView.swift; sourceTree = ""; }; + 512AF9C1236ED52C0066F8BE /* ImageHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageHeaderView.swift; sourceTree = ""; }; 512AF9DC236F05230066F8BE /* InteractiveLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveLabel.swift; sourceTree = ""; }; 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedTableViewSectionHeader.swift; sourceTree = ""; }; 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISplitViewController-Extensions.swift"; sourceTree = ""; }; @@ -1696,7 +1696,6 @@ 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */, 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */, 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */, - 512AF9C1236ED52C0066F8BE /* InspectorImageHeaderView.swift */, ); path = Inspector; sourceTree = ""; @@ -1839,6 +1838,7 @@ children = ( 51F85BFA2275D85000C787DC /* Array-Extensions.swift */, 51F85BF42273625800C787DC /* Bundle-Extensions.swift */, + 512AF9C1236ED52C0066F8BE /* ImageHeaderView.swift */, 512AF9DC236F05230066F8BE /* InteractiveLabel.swift */, 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */, 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */, @@ -1846,6 +1846,7 @@ 512363372369155100951F16 /* RoundedProgressView.swift */, 51C45250226506F400C03939 /* String-Extensions.swift */, 51934CC1230F5963006127BE /* ThemedNavigationController.swift */, + 5108F6D723763094001ABC45 /* TickMarkSlider.swift */, 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */, 51F85BF622749FA100C787DC /* UIFont-Extensions.swift */, 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */, @@ -1853,7 +1854,6 @@ 51FFF0C3235EE8E5002762AA /* VibrantButton.swift */, 5186A634235EF3A800C97195 /* VibrantLabel.swift */, 5F323808231DF9F000706F6B /* VibrantTableViewCell.swift */, - 5108F6D723763094001ABC45 /* TickMarkSlider.swift */, ); path = "UIKit Extensions"; sourceTree = ""; @@ -3962,7 +3962,7 @@ 84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */, 51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */, 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, - 512AF9C2236ED52C0066F8BE /* InspectorImageHeaderView.swift in Sources */, + 512AF9C2236ED52C0066F8BE /* ImageHeaderView.swift in Sources */, 51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */, 51C45290226509C100C03939 /* PseudoFeed.swift in Sources */, 51C452A922650DC600C03939 /* ArticleRenderer.swift in Sources */, diff --git a/iOS/Account/FeedbinAccountViewController.swift b/iOS/Account/FeedbinAccountViewController.swift index 3196b5880..71f4fce38 100644 --- a/iOS/Account/FeedbinAccountViewController.swift +++ b/iOS/Account/FeedbinAccountViewController.swift @@ -39,8 +39,24 @@ class FeedbinAccountViewController: UITableViewController { NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: UITextField.textDidChangeNotification, object: emailTextField) NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: UITextField.textDidChangeNotification, object: passwordTextField) + + tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") + } + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return section == 0 ? 64.0 : super.tableView(tableView, heightForHeaderInSection: section) } + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if section == 0 { + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView + headerView.imageView.image = AppAssets.image(for: .feedbin) + return headerView + } else { + return super.tableView(tableView, viewForHeaderInSection: section) + } + } + @IBAction func cancel(_ sender: Any) { dismiss(animated: true, completion: nil) delegate?.dismiss() diff --git a/iOS/Account/LocalAccountViewController.swift b/iOS/Account/LocalAccountViewController.swift index 5abf1f436..6aed1eddf 100644 --- a/iOS/Account/LocalAccountViewController.swift +++ b/iOS/Account/LocalAccountViewController.swift @@ -19,6 +19,8 @@ class LocalAccountViewController: UITableViewController { super.viewDidLoad() navigationItem.title = Account.defaultLocalAccountName nameTextField.delegate = self + + tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") } @IBAction func cancel(_ sender: Any) { @@ -33,6 +35,20 @@ class LocalAccountViewController: UITableViewController { delegate?.dismiss() } + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return section == 0 ? 64.0 : super.tableView(tableView, heightForHeaderInSection: section) + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if section == 0 { + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView + headerView.imageView.image = AppAssets.image(for: .onMyMac) + return headerView + } else { + return super.tableView(tableView, viewForHeaderInSection: section) + } + } + } extension LocalAccountViewController: UITextFieldDelegate { diff --git a/iOS/Inspector/AccountInspectorViewController.swift b/iOS/Inspector/AccountInspectorViewController.swift index 09d41e12c..cc0095643 100644 --- a/iOS/Inspector/AccountInspectorViewController.swift +++ b/iOS/Inspector/AccountInspectorViewController.swift @@ -36,7 +36,7 @@ class AccountInspectorViewController: UITableViewController { navigationItem.leftBarButtonItem = doneBarButtonItem } - tableView.register(InspectorImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") + tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") } @@ -135,7 +135,7 @@ extension AccountInspectorViewController { guard let account = account else { return nil } if section == 0 { - let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! InspectorImageHeaderView + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView headerView.imageView.image = AppAssets.image(for: account.type) return headerView } else { diff --git a/iOS/Inspector/InspectorImageHeaderView.swift b/iOS/UIKit Extensions/ImageHeaderView.swift similarity index 88% rename from iOS/Inspector/InspectorImageHeaderView.swift rename to iOS/UIKit Extensions/ImageHeaderView.swift index c236b30aa..3c26eda89 100644 --- a/iOS/Inspector/InspectorImageHeaderView.swift +++ b/iOS/UIKit Extensions/ImageHeaderView.swift @@ -1,5 +1,5 @@ // -// InspectorHeaderView.swift +// ImageHeaderView.swift // NetNewsWire-iOS // // Created by Maurice Parker on 11/3/19. @@ -8,7 +8,7 @@ import UIKit -class InspectorImageHeaderView: UITableViewHeaderFooterView { +class ImageHeaderView: UITableViewHeaderFooterView { var imageView = UIImageView() From 397d8e8ffa2f1ffe58477c189063c11f259c9df6 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 19:44:01 -0600 Subject: [PATCH 216/237] Update picker for Share Extension to be hierarchical and use icons. Issue #1269 --- NetNewsWire.xcodeproj/project.pbxproj | 34 +++++++---- .../Data}/AddWebFeedDefaultContainer.swift | 2 +- Shared/Extensions/IconImage.swift | 23 ++++++++ .../FlattenedAccountFolderPickerData.swift | 50 ----------------- iOS/Add/AddWebFeedFolderViewController.swift | 4 +- iOS/Add/AddWebFeedViewController.swift | 2 +- iOS/AppDefaults.swift | 8 +-- .../Cell/MasterTimelineCellData.swift | 4 +- .../Cell/MasterTimelineCellLayout.swift | 2 +- .../MasterTimelineIconSize.swift | 32 ----------- .../MasterTimelineViewController.swift | 2 +- .../TimelineCustomizerViewController.swift | 2 +- .../ShareFolderPickerAccountCell.xib | 49 ++++++++++++++++ .../ShareFolderPickerCell.swift | 15 +++++ .../ShareFolderPickerController.swift | 56 +++++++++++++++---- .../ShareFolderPickerFolderCell.xib | 49 ++++++++++++++++ iOS/ShareExtension/ShareViewController.swift | 33 ++++++----- 17 files changed, 236 insertions(+), 131 deletions(-) rename {iOS/Add => Shared/Data}/AddWebFeedDefaultContainer.swift (95%) delete mode 100644 Shared/Tree/FlattenedAccountFolderPickerData.swift delete mode 100644 iOS/MasterTimeline/MasterTimelineIconSize.swift create mode 100644 iOS/ShareExtension/ShareFolderPickerAccountCell.xib create mode 100644 iOS/ShareExtension/ShareFolderPickerCell.swift create mode 100644 iOS/ShareExtension/ShareFolderPickerFolderCell.xib diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index ef5834836..3a5c42bae 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 5108F6B72375E612001ABC45 /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; 5108F6D22375EED2001ABC45 /* TimelineCustomizerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */; }; 5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D32375EEEF001ABC45 /* TimelinePreviewTableViewController.swift */; }; - 5108F6D623762309001ABC45 /* MasterTimelineIconSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D523762309001ABC45 /* MasterTimelineIconSize.swift */; }; 5108F6D823763094001ABC45 /* TickMarkSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D723763094001ABC45 /* TickMarkSlider.swift */; }; 51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; }; 5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */; }; @@ -129,6 +128,14 @@ 51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16995235E10D600EB091F /* AboutViewController.swift */; }; 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */; }; 51A66685238075AE00CB272D /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; + 51A9A5E02380C3F10033AADF /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; + 51A9A5E12380C4FE0033AADF /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45255226507D200C03939 /* AppDefaults.swift */; }; + 51A9A5E42380C8880033AADF /* ShareFolderPickerAccountCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51A9A5E32380C8870033AADF /* ShareFolderPickerAccountCell.xib */; }; + 51A9A5E62380C8B20033AADF /* ShareFolderPickerFolderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51A9A5E52380C8B20033AADF /* ShareFolderPickerFolderCell.xib */; }; + 51A9A5E82380CA130033AADF /* ShareFolderPickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A9A5E72380CA130033AADF /* ShareFolderPickerCell.swift */; }; + 51A9A5ED2380D6000033AADF /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45254226507D200C03939 /* AppAssets.swift */; }; + 51A9A5EE2380D6080033AADF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC9B2262A1A900D921D6 /* Assets.xcassets */; }; + 51A9A5EF2380D63B0033AADF /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; 51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; }; 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; }; 51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; }; @@ -212,8 +219,6 @@ 51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; }; 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB3C229AB08300645299 /* ErrorHandler.swift */; }; 51E43962238037C400015C31 /* AddWebFeedFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */; }; - 51E4397D23805A6D00015C31 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4397C23805A6C00015C31 /* FlattenedAccountFolderPickerData.swift */; }; - 51E4397E23805A6D00015C31 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4397C23805A6C00015C31 /* FlattenedAccountFolderPickerData.swift */; }; 51E4398023805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4397F23805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift */; }; 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; @@ -1208,7 +1213,6 @@ 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 = ""; }; - 5108F6D523762309001ABC45 /* MasterTimelineIconSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineIconSize.swift; sourceTree = ""; }; 5108F6D723763094001ABC45 /* TickMarkSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TickMarkSlider.swift; sourceTree = ""; }; 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorIconHeaderView.swift; sourceTree = ""; }; @@ -1292,6 +1296,9 @@ 51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = ""; }; 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedDefaultContainer.swift; sourceTree = ""; }; + 51A9A5E32380C8870033AADF /* ShareFolderPickerAccountCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerAccountCell.xib; sourceTree = ""; }; + 51A9A5E52380C8B20033AADF /* ShareFolderPickerFolderCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerFolderCell.xib; sourceTree = ""; }; + 51A9A5E72380CA130033AADF /* ShareFolderPickerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerCell.swift; sourceTree = ""; }; 51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; 51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; @@ -1323,7 +1330,6 @@ 51E3EB32229AB02C00645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 51E3EB3C229AB08300645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedFolderViewController.swift; sourceTree = ""; }; - 51E4397C23805A6C00015C31 /* FlattenedAccountFolderPickerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlattenedAccountFolderPickerData.swift; sourceTree = ""; }; 51E4397F23805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedFolderTableViewCell.swift; sourceTree = ""; }; 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleStatusSyncTimer.swift; sourceTree = ""; }; 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicButton.swift; sourceTree = ""; }; @@ -1711,7 +1717,6 @@ 512E08DD22687FA000BDCFDD /* Tree */ = { isa = PBXGroup; children = ( - 51E4397C23805A6C00015C31 /* FlattenedAccountFolderPickerData.swift */, 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */, 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */, ); @@ -1750,6 +1755,9 @@ children = ( 513C5CE8232571C2003D4054 /* ShareViewController.swift */, 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */, + 51A9A5E72380CA130033AADF /* ShareFolderPickerCell.swift */, + 51A9A5E32380C8870033AADF /* ShareFolderPickerAccountCell.xib */, + 51A9A5E52380C8B20033AADF /* ShareFolderPickerFolderCell.xib */, 513C5CEA232571C2003D4054 /* MainInterface.storyboard */, 513C5CED232571C2003D4054 /* Info.plist */, 515D4FCB2325815A00EE1167 /* SafariExt.js */, @@ -1892,7 +1900,6 @@ 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */, FFD43E372340F320009E5CA3 /* UndoAvailableAlertController.swift */, 51C4526F2265091600C03939 /* Cell */, - 5108F6D523762309001ABC45 /* MasterTimelineIconSize.swift */, ); path = MasterTimeline; sourceTree = ""; @@ -1935,7 +1942,6 @@ 51C452842265093600C03939 /* AddWebFeedViewController.swift */, 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */, 51E4397F23805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift */, - 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */, ); path = Add; sourceTree = ""; @@ -2203,6 +2209,7 @@ 849A97561ED9EB0D007D329B /* Data */ = { isa = PBXGroup; children = ( + 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */, 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */, 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */, 5108F6B52375E612001ABC45 /* CacheCleaner.swift */, @@ -3317,7 +3324,10 @@ buildActionMask = 2147483647; files = ( 515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */, + 51A9A5E62380C8B20033AADF /* ShareFolderPickerFolderCell.xib in Resources */, + 51A9A5E42380C8880033AADF /* ShareFolderPickerAccountCell.xib in Resources */, 513C5CEC232571C2003D4054 /* MainInterface.storyboard in Resources */, + 51A9A5EE2380D6080033AADF /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3702,7 +3712,11 @@ 515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */, 513C5CE9232571C2003D4054 /* ShareViewController.swift in Sources */, 51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */, - 51E4397E23805A6D00015C31 /* FlattenedAccountFolderPickerData.swift in Sources */, + 51A9A5E02380C3F10033AADF /* AddWebFeedDefaultContainer.swift in Sources */, + 51A9A5E82380CA130033AADF /* ShareFolderPickerCell.swift in Sources */, + 51A9A5EF2380D63B0033AADF /* IconImage.swift in Sources */, + 51A9A5ED2380D6000033AADF /* AppAssets.swift in Sources */, + 51A9A5E12380C4FE0033AADF /* AppDefaults.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3885,7 +3899,6 @@ 512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */, 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */, 51EF0F79227716380050506E /* ColorHash.swift in Sources */, - 51E4397D23805A6D00015C31 /* FlattenedAccountFolderPickerData.swift in Sources */, 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */, 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */, 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */, @@ -4004,7 +4017,6 @@ 51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */, 511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */, 51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */, - 5108F6D623762309001ABC45 /* MasterTimelineIconSize.swift in Sources */, 51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */, 51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */, 51934CCB230F599B006127BE /* ThemedNavigationController.swift in Sources */, diff --git a/iOS/Add/AddWebFeedDefaultContainer.swift b/Shared/Data/AddWebFeedDefaultContainer.swift similarity index 95% rename from iOS/Add/AddWebFeedDefaultContainer.swift rename to Shared/Data/AddWebFeedDefaultContainer.swift index 8e52b17f8..41e158ce9 100644 --- a/iOS/Add/AddWebFeedDefaultContainer.swift +++ b/Shared/Data/AddWebFeedDefaultContainer.swift @@ -27,7 +27,7 @@ struct AddWebFeedDefaultContainer { } - static func storeDefaultContainer(_ container: Container) { + static func saveDefaultContainer(_ container: Container) { AppDefaults.addWebFeedAccountID = container.account?.accountID if let folder = container as? Folder { AppDefaults.addWebFeedFolderName = folder.nameForDisplay diff --git a/Shared/Extensions/IconImage.swift b/Shared/Extensions/IconImage.swift index c02d32926..9d82daad1 100644 --- a/Shared/Extensions/IconImage.swift +++ b/Shared/Extensions/IconImage.swift @@ -64,3 +64,26 @@ extension CGImage { } } + + +enum IconSize: Int, CaseIterable { + case small = 1 + case medium = 2 + case large = 3 + + private static let smallDimension = CGFloat(integerLiteral: 24) + private static let mediumDimension = CGFloat(integerLiteral: 36) + private static let largeDimension = CGFloat(integerLiteral: 48) + + var size: CGSize { + switch self { + case .small: + return CGSize(width: IconSize.smallDimension, height: IconSize.smallDimension) + case .medium: + return CGSize(width: IconSize.mediumDimension, height: IconSize.mediumDimension) + case .large: + return CGSize(width: IconSize.largeDimension, height: IconSize.largeDimension) + } + } + +} diff --git a/Shared/Tree/FlattenedAccountFolderPickerData.swift b/Shared/Tree/FlattenedAccountFolderPickerData.swift deleted file mode 100644 index 9d23c4aa4..000000000 --- a/Shared/Tree/FlattenedAccountFolderPickerData.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// FlattenedAccountFolderPickerData.swift -// NetNewsWire -// -// Created by Maurice Parker on 4/16/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - - -import Foundation -import Account -import RSCore -import RSTree - -struct FlattenedAccountFolderPickerData { - - var containerNames = [String]() - var containers = [Container]() - - init() { - - let treeControllerDelegate = FolderTreeControllerDelegate() - let treeController = TreeController(delegate: treeControllerDelegate) - - treeController.rootNode.childNodes.forEach { node in - - guard let acctNameProvider = node.representedObject as? DisplayNameProvider else { - return - } - - let acctName = acctNameProvider.nameForDisplay - containerNames.append(acctName) - containers.append(node.representedObject as! Container) - - for child in node.childNodes { - - guard let childContainer = child.representedObject as? Container else { - return - } - let childName = (childContainer as! DisplayNameProvider).nameForDisplay - containerNames.append("\(acctName) / \(childName)") - containers.append(childContainer) - - } - - } - - } - -} diff --git a/iOS/Add/AddWebFeedFolderViewController.swift b/iOS/Add/AddWebFeedFolderViewController.swift index 8ed6d0227..a97aee90f 100644 --- a/iOS/Add/AddWebFeedFolderViewController.swift +++ b/iOS/Add/AddWebFeedFolderViewController.swift @@ -62,8 +62,10 @@ class AddWebFeedFolderViewController: UITableViewController { if let compContainer = initialContainer, container === compContainer { cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none } - + return cell } diff --git a/iOS/Add/AddWebFeedViewController.swift b/iOS/Add/AddWebFeedViewController.swift index 465fdaee2..e663cc9d2 100644 --- a/iOS/Add/AddWebFeedViewController.swift +++ b/iOS/Add/AddWebFeedViewController.swift @@ -138,7 +138,7 @@ extension AddWebFeedViewController: AddWebFeedFolderViewControllerDelegate { func didSelect(container: Container) { self.container = container updateFolderLabel() - AddWebFeedDefaultContainer.storeDefaultContainer(container) + AddWebFeedDefaultContainer.saveDefaultContainer(container) } } diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index 341ecd7d5..0942df915 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -119,10 +119,10 @@ struct AppDefaults { } } - static var timelineIconSize: MasterTimelineIconSize { + static var timelineIconSize: IconSize { get { let rawValue = AppDefaults.shared.integer(forKey: Key.timelineIconSize) - return MasterTimelineIconSize(rawValue: rawValue) ?? MasterTimelineIconSize.medium + return IconSize(rawValue: rawValue) ?? IconSize.medium } set { AppDefaults.shared.set(newValue.rawValue, forKey: Key.timelineIconSize) @@ -133,7 +133,7 @@ struct AppDefaults { let defaults: [String : Any] = [Key.lastImageCacheFlushDate: Date(), Key.timelineGroupByFeed: false, Key.timelineNumberOfLines: 2, - Key.timelineIconSize: MasterTimelineIconSize.medium.rawValue, + Key.timelineIconSize: IconSize.medium.rawValue, Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, Key.displayUndoAvailableTip: true] AppDefaults.shared.register(defaults: defaults) @@ -202,5 +202,3 @@ private extension AppDefaults { } } - - diff --git a/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift b/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift index c801a4c72..707cea1f4 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift @@ -22,9 +22,9 @@ struct MasterTimelineCellData { let read: Bool let starred: Bool let numberOfLines: Int - let iconSize: MasterTimelineIconSize + let iconSize: IconSize - init(article: Article, showFeedName: Bool, feedName: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: UIImage?, numberOfLines: Int, iconSize: MasterTimelineIconSize) { + init(article: Article, showFeedName: Bool, feedName: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: UIImage?, numberOfLines: Int, iconSize: IconSize) { self.title = ArticleStringFormatter.truncatedTitle(article) self.summary = ArticleStringFormatter.truncatedSummary(article) diff --git a/iOS/MasterTimeline/Cell/MasterTimelineCellLayout.swift b/iOS/MasterTimeline/Cell/MasterTimelineCellLayout.swift index b7d99cc32..b93c7601b 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineCellLayout.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineCellLayout.swift @@ -42,7 +42,7 @@ extension MasterTimelineCellLayout { return r } - static func rectForIconView(_ point: CGPoint, iconSize: MasterTimelineIconSize) -> CGRect { + static func rectForIconView(_ point: CGPoint, iconSize: IconSize) -> CGRect { var r = CGRect.zero r.size = iconSize.size r.origin.x = point.x diff --git a/iOS/MasterTimeline/MasterTimelineIconSize.swift b/iOS/MasterTimeline/MasterTimelineIconSize.swift deleted file mode 100644 index d06629e59..000000000 --- a/iOS/MasterTimeline/MasterTimelineIconSize.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// MasterTimelineIconSize.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 11/8/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import Foundation -import CoreGraphics - -enum MasterTimelineIconSize: Int, CaseIterable { - case small = 1 - case medium = 2 - case large = 3 - - private static let smallDimension = CGFloat(integerLiteral: 24) - private static let mediumDimension = CGFloat(integerLiteral: 36) - private static let largeDimension = CGFloat(integerLiteral: 48) - - var size: CGSize { - switch self { - case .small: - return CGSize(width: MasterTimelineIconSize.smallDimension, height: MasterTimelineIconSize.smallDimension) - case .medium: - return CGSize(width: MasterTimelineIconSize.mediumDimension, height: MasterTimelineIconSize.mediumDimension) - case .large: - return CGSize(width: MasterTimelineIconSize.largeDimension, height: MasterTimelineIconSize.largeDimension) - } - } - -} diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index ec9cce0a0..a5f234eb1 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -15,7 +15,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner private var titleView: MasterTimelineTitleView? private var numberOfTextLines = 0 - private var iconSize = MasterTimelineIconSize.medium + private var iconSize = IconSize.medium @IBOutlet weak var markAllAsReadButton: UIBarButtonItem! @IBOutlet weak var firstUnreadButton: UIBarButtonItem! diff --git a/iOS/Settings/TimelineCustomizerViewController.swift b/iOS/Settings/TimelineCustomizerViewController.swift index 284b5785d..4a3b4af4c 100644 --- a/iOS/Settings/TimelineCustomizerViewController.swift +++ b/iOS/Settings/TimelineCustomizerViewController.swift @@ -47,7 +47,7 @@ class TimelineCustomizerViewController: UIViewController { } @IBAction func iconSizeChanged(_ sender: Any) { - guard let iconSize = MasterTimelineIconSize(rawValue: Int(iconSizeSlider.value.rounded())) else { return } + guard let iconSize = IconSize(rawValue: Int(iconSizeSlider.value.rounded())) else { return } AppDefaults.timelineIconSize = iconSize updatePreview() } diff --git a/iOS/ShareExtension/ShareFolderPickerAccountCell.xib b/iOS/ShareExtension/ShareFolderPickerAccountCell.xib new file mode 100644 index 000000000..4fd8499aa --- /dev/null +++ b/iOS/ShareExtension/ShareFolderPickerAccountCell.xib @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/ShareExtension/ShareFolderPickerCell.swift b/iOS/ShareExtension/ShareFolderPickerCell.swift new file mode 100644 index 000000000..6d37f8365 --- /dev/null +++ b/iOS/ShareExtension/ShareFolderPickerCell.swift @@ -0,0 +1,15 @@ +// +// ShareFolderPickerCell.swift +// NetNewsWire iOS Share Extension +// +// Created by Maurice Parker on 11/16/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class ShareFolderPickerCell: UITableViewCell { + + @IBOutlet weak var icon: UIImageView! + @IBOutlet weak var label: UILabel! +} diff --git a/iOS/ShareExtension/ShareFolderPickerController.swift b/iOS/ShareExtension/ShareFolderPickerController.swift index d20b3e59d..1ffa6fcf9 100644 --- a/iOS/ShareExtension/ShareFolderPickerController.swift +++ b/iOS/ShareExtension/ShareFolderPickerController.swift @@ -7,21 +7,30 @@ // import UIKit +import RSCore import Account protocol ShareFolderPickerControllerDelegate: class { - func shareFolderPickerDidSelect(_ container: Container, _ selectionName: String) + func shareFolderPickerDidSelect(_ container: Container) } class ShareFolderPickerController: UITableViewController { - var pickerData: FlattenedAccountFolderPickerData? var selectedContainer: Container? - + var containers = [Container]() + weak var delegate: ShareFolderPickerControllerDelegate? override func viewDidLoad() { - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") + for account in AccountManager.shared.sortedActiveAccounts { + containers.append(account) + if let sortedFolders = account.sortedFolders { + containers.append(contentsOf: sortedFolders) + } + } + + tableView.register(UINib(nibName: "ShareFolderPickerAccountCell", bundle: Bundle.main), forCellReuseIdentifier: "AccountCell") + tableView.register(UINib(nibName: "ShareFolderPickerFolderCell", bundle: Bundle.main), forCellReuseIdentifier: "FolderCell") } override func numberOfSections(in tableView: UITableView) -> Int { @@ -29,23 +38,48 @@ class ShareFolderPickerController: UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return pickerData?.containerNames.count ?? 0 + return containers.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) - cell.textLabel?.text = pickerData?.containerNames[indexPath.row] ?? "" - if pickerData?.containers[indexPath.row] === selectedContainer { + let container = containers[indexPath.row] + let cell: ShareFolderPickerCell = { + if container is Account { + return tableView.dequeueReusableCell(withIdentifier: "AccountCell", for: indexPath) as! ShareFolderPickerCell + } else { + return tableView.dequeueReusableCell(withIdentifier: "FolderCell", for: indexPath) as! ShareFolderPickerCell + } + }() + + if let account = container as? Account { + cell.icon.image = AppAssets.image(for: account.type) + } else { + cell.icon.image = AppAssets.masterFolderImage.image + } + + if let displayNameProvider = container as? DisplayNameProvider { + cell.label?.text = displayNameProvider.nameForDisplay + } + + if let compContainer = selectedContainer, container === compContainer { cell.accessoryType = .checkmark } else { cell.accessoryType = .none } - return cell + + return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let pickerData = pickerData else { return } - delegate?.shareFolderPickerDidSelect(pickerData.containers[indexPath.row], pickerData.containerNames[indexPath.row]) + let container = containers[indexPath.row] + + if let account = container as? Account, account.behaviors.contains(.disallowFeedInRootFolder) { + tableView.selectRow(at: nil, animated: false, scrollPosition: .none) + } else { + let cell = tableView.cellForRow(at: indexPath) + cell?.accessoryType = .checkmark + delegate?.shareFolderPickerDidSelect(container) + } } } diff --git a/iOS/ShareExtension/ShareFolderPickerFolderCell.xib b/iOS/ShareExtension/ShareFolderPickerFolderCell.xib new file mode 100644 index 000000000..0b85b9415 --- /dev/null +++ b/iOS/ShareExtension/ShareFolderPickerFolderCell.xib @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/ShareExtension/ShareViewController.swift b/iOS/ShareExtension/ShareViewController.swift index 6e929c701..8f2abcb2e 100644 --- a/iOS/ShareExtension/ShareViewController.swift +++ b/iOS/ShareExtension/ShareViewController.swift @@ -16,8 +16,6 @@ import RSTree class ShareViewController: SLComposeServiceViewController, ShareFolderPickerControllerDelegate { - private var pickerData: FlattenedAccountFolderPickerData? - private var url: URL? private var container: Container? private var folderItem: SLComposeSheetConfigurationItem! @@ -26,11 +24,7 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont AccountManager.shared = AccountManager() - pickerData = FlattenedAccountFolderPickerData() - - if pickerData?.containers.count ?? 0 > 0 { - container = pickerData?.containers[0] - } + container = AddWebFeedDefaultContainer.defaultContainer title = "NetNewsWire" placeholder = "Feed Name (Optional)" @@ -130,9 +124,10 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont } } - func shareFolderPickerDidSelect(_ container: Container, _ selectionName: String) { + func shareFolderPickerDidSelect(_ container: Container) { + AddWebFeedDefaultContainer.saveDefaultContainer(container) self.container = container - self.folderItem.value = selectionName + updateFolderItemValue() self.popConfigurationViewController() } @@ -145,10 +140,7 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont folderItem = SLComposeSheetConfigurationItem() folderItem.title = "Folder" - - if let nameProvider = container as? DisplayNameProvider { - folderItem.value = nameProvider.nameForDisplay - } + updateFolderItemValue() folderItem.tapHandler = { @@ -156,7 +148,6 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont folderPickerController.navigationController?.title = NSLocalizedString("Folder", comment: "Folder") folderPickerController.delegate = self - folderPickerController.pickerData = self.pickerData folderPickerController.selectedContainer = self.container self.pushConfigurationViewController(folderPickerController) @@ -168,3 +159,17 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont } } + +private extension ShareViewController { + + func updateFolderItemValue() { + if let containerName = (container as? DisplayNameProvider)?.nameForDisplay { + if container is Folder { + self.folderItem.value = "\(container?.account?.nameForDisplay ?? "") / \(containerName)" + } else { + self.folderItem.value = containerName + } + } + } + +} From 991ef26a1f5fba86e2e6912329d86c3d5efebf29 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 19:57:05 -0600 Subject: [PATCH 217/237] Change to remember last selected Folder when adding a Feed. Issue #1306 --- Mac/AppDefaults.swift | 18 ++++++++++++++---- .../AddFeed/AddFeedWindowController.swift | 17 +++++++++-------- NetNewsWire.xcodeproj/project.pbxproj | 4 ++++ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Mac/AppDefaults.swift b/Mac/AppDefaults.swift index 6ac0938ea..d5eef0fe3 100644 --- a/Mac/AppDefaults.swift +++ b/Mac/AppDefaults.swift @@ -27,7 +27,8 @@ struct AppDefaults { static let openInBrowserInBackground = "openInBrowserInBackground" static let mainWindowWidths = "mainWindowWidths" static let refreshInterval = "refreshInterval" - static let addFeedAccountID = "addFeedAccountID" + static let addWebFeedAccountID = "addWebFeedAccountID" + static let addWebFeedFolderName = "addWebFeedFolderName" static let addFolderAccountID = "addFolderAccountID" static let importOPMLAccountID = "importOPMLAccountID" static let exportOPMLAccountID = "exportOPMLAccountID" @@ -99,15 +100,24 @@ struct AppDefaults { } } - static var addFeedAccountID: String? { + static var addWebFeedAccountID: String? { get { - return string(for: Key.addFeedAccountID) + return string(for: Key.addWebFeedAccountID) } set { - setString(for: Key.addFeedAccountID, newValue) + setString(for: Key.addWebFeedAccountID, newValue) } } + static var addWebFeedFolderName: String? { + get { + return string(for: Key.addWebFeedFolderName) + } + set { + setString(for: Key.addWebFeedFolderName, newValue) + } + } + static var addFolderAccountID: String? { get { return string(for: Key.addFolderAccountID) diff --git a/Mac/MainWindow/AddFeed/AddFeedWindowController.swift b/Mac/MainWindow/AddFeed/AddFeedWindowController.swift index 5c05b2af5..df5f87868 100644 --- a/Mac/MainWindow/AddFeed/AddFeedWindowController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedWindowController.swift @@ -69,11 +69,16 @@ class AddFeedWindowController : NSWindowController { } folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController.rootNode) + if let account = initialAccount { FolderTreeMenu.select(account: account, folder: initialFolder, in: folderPopupButton) - } else if let accountID = AppDefaults.addFeedAccountID { - if let account = AccountManager.shared.existingAccount(with: accountID) { - FolderTreeMenu.select(account: account, folder: nil, in: folderPopupButton) + } else if let container = AddWebFeedDefaultContainer.defaultContainer { + if let folder = container as? Folder, let account = folder.account { + FolderTreeMenu.select(account: account, folder: folder, in: folderPopupButton) + } else { + if let account = container as? Account { + FolderTreeMenu.select(account: account, folder: nil, in: folderPopupButton) + } } } @@ -100,11 +105,7 @@ class AddFeedWindowController : NSWindowController { } let container = selectedContainer()! - if let selectedAccount = container as? Account { - AppDefaults.addFeedAccountID = selectedAccount.accountID - } else if let selectedFolder = container as? Folder, let selectedAccount = selectedFolder.account { - AppDefaults.addFeedAccountID = selectedAccount.accountID - } + AddWebFeedDefaultContainer.saveDefaultContainer(container) delegate?.addFeedWindowController(self, userEnteredURL: url, userEnteredTitle: userEnteredTitle, container: container) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 3a5c42bae..4e4a702a5 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -136,6 +136,8 @@ 51A9A5ED2380D6000033AADF /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45254226507D200C03939 /* AppAssets.swift */; }; 51A9A5EE2380D6080033AADF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC9B2262A1A900D921D6 /* Assets.xcassets */; }; 51A9A5EF2380D63B0033AADF /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; + 51A9A5F22380DE520033AADF /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; + 51A9A5F32380DE530033AADF /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; 51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; }; 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; }; 51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; }; @@ -3853,6 +3855,7 @@ 65ED4025235DEF6C0081F399 /* DetailWebView.swift in Sources */, 65ED4026235DEF6C0081F399 /* TimelineTableRowView.swift in Sources */, 65ED4027235DEF6C0081F399 /* UnreadIndicatorView.swift in Sources */, + 51A9A5F22380DE520033AADF /* AddWebFeedDefaultContainer.swift in Sources */, 65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */, 65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */, 65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */, @@ -4059,6 +4062,7 @@ D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */, D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */, 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */, + 51A9A5F32380DE530033AADF /* AddWebFeedDefaultContainer.swift in Sources */, 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */, D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */, 8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */, From cc93b2f8f1f1fc0a770927bd558c1d425e1f5e14 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 16 Nov 2019 21:27:23 -0600 Subject: [PATCH 218/237] Add title to new Folder picker --- iOS/Add/Add.storyboard | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index 66f95e687..3332621a7 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -217,7 +217,7 @@ - + From 0e60c19338419336a258068ae86ad04328e7d734 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 17 Nov 2019 09:49:26 -0600 Subject: [PATCH 219/237] Force modal in modal dialogs to always have full size navigation bars. Issue #1301 --- NetNewsWire.xcodeproj/project.pbxproj | 4 ++++ iOS/Account/Account.storyboard | 8 +++---- iOS/Add/Add.storyboard | 4 ++-- .../ModalNavigationController.swift | 21 +++++++++++++++++++ 4 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 iOS/UIKit Extensions/ModalNavigationController.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 4e4a702a5..0277c54d3 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -138,6 +138,7 @@ 51A9A5EF2380D63B0033AADF /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; 51A9A5F22380DE520033AADF /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; 51A9A5F32380DE530033AADF /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; + 51A9A5F52380F6A60033AADF /* ModalNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A9A5F42380F6A60033AADF /* ModalNavigationController.swift */; }; 51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; }; 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; }; 51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; }; @@ -1301,6 +1302,7 @@ 51A9A5E32380C8870033AADF /* ShareFolderPickerAccountCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerAccountCell.xib; sourceTree = ""; }; 51A9A5E52380C8B20033AADF /* ShareFolderPickerFolderCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerFolderCell.xib; sourceTree = ""; }; 51A9A5E72380CA130033AADF /* ShareFolderPickerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerCell.swift; sourceTree = ""; }; + 51A9A5F42380F6A60033AADF /* ModalNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalNavigationController.swift; sourceTree = ""; }; 51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; 51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; @@ -1864,6 +1866,7 @@ 51FFF0C3235EE8E5002762AA /* VibrantButton.swift */, 5186A634235EF3A800C97195 /* VibrantLabel.swift */, 5F323808231DF9F000706F6B /* VibrantTableViewCell.swift */, + 51A9A5F42380F6A60033AADF /* ModalNavigationController.swift */, ); path = "UIKit Extensions"; sourceTree = ""; @@ -3903,6 +3906,7 @@ 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */, 51EF0F79227716380050506E /* ColorHash.swift in Sources */, 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */, + 51A9A5F52380F6A60033AADF /* ModalNavigationController.swift in Sources */, 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */, 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */, 5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */, diff --git a/iOS/Account/Account.storyboard b/iOS/Account/Account.storyboard index fb90e6288..5da28a067 100644 --- a/iOS/Account/Account.storyboard +++ b/iOS/Account/Account.storyboard @@ -7,10 +7,10 @@ - + - + @@ -23,10 +23,10 @@ - + - + diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index 3332621a7..665bec62b 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -117,10 +117,10 @@ - + - + diff --git a/iOS/UIKit Extensions/ModalNavigationController.swift b/iOS/UIKit Extensions/ModalNavigationController.swift new file mode 100644 index 000000000..cbc7750ba --- /dev/null +++ b/iOS/UIKit Extensions/ModalNavigationController.swift @@ -0,0 +1,21 @@ +// +// ModalNavigationController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 11/16/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class ModalNavigationController: UINavigationController { + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + // This hack is to resolve https://github.com/brentsimmons/NetNewsWire/issues/1301 + let frame = navigationBar.frame + navigationBar.frame = CGRect(x: frame.minX, y: frame.minY, width: frame.size.width, height: 64.0) + } + +} From 70aa3f2091f7895fdd8fea65ea497f1ab56f77aa Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 17 Nov 2019 10:30:50 -0600 Subject: [PATCH 220/237] Clear up compiler warnings and auto layout constraint warnings. --- iOS/Account/Account.storyboard | 6 +++--- iOS/Inspector/Inspector.storyboard | 6 ++---- iOS/SceneCoordinator.swift | 5 +---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/iOS/Account/Account.storyboard b/iOS/Account/Account.storyboard index 5da28a067..7cedd59df 100644 --- a/iOS/Account/Account.storyboard +++ b/iOS/Account/Account.storyboard @@ -110,7 +110,7 @@ - + @@ -225,12 +225,12 @@ - + - + diff --git a/iOS/Inspector/Inspector.storyboard b/iOS/Inspector/Inspector.storyboard index 6fc8a3149..a3b96470b 100644 --- a/iOS/Inspector/Inspector.storyboard +++ b/iOS/Inspector/Inspector.storyboard @@ -94,9 +94,8 @@ - + - @@ -135,10 +134,9 @@ - - + diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 77db16270..90bc20a30 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -255,10 +255,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } var isTimelineUnreadAvailable: Bool { - if let unreadProvider = timelineFeed as? UnreadCountProvider { - return unreadProvider.unreadCount > 0 - } - return false + return timelineFeed?.unreadCount ?? 0 > 0 } var isAnyUnreadAvailable: Bool { From 7f64db1b3ade048c58729db4af302aa3e9e3da1c Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 17 Nov 2019 11:47:08 -0600 Subject: [PATCH 221/237] Fixed Folder labels that looked out of balance. --- iOS/Add/Add.storyboard | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index 665bec62b..b489e9d72 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -74,7 +74,7 @@