From 1ad6bfb86b5cd01b208bbe613421aaf8ca001de5 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Sat, 25 May 2019 15:45:55 -0500 Subject: [PATCH 001/119] Add basic building instructions I would not have known to look in xcconfig/NetNewsWire_target.xcconfig to find these instructions. I only found them because I was searching around trying to figure out why NetNewsWire wouldn't build for me and found #576. Why not include some instructions in the README? --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index b858c7d29..df451ee00 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,38 @@ That said, we will seriously consider any pull requests we do get. Just note tha It’s probably a good idea to let us know first what you’d like to do. The best place for that is definitely the [Slack group](https://join.slack.com/t/netnewswire/shared_invite/enQtNjM4MDA1MjQzMDkzLTNlNjBhOWVhYzdhYjA4ZWFhMzQ1MTUxYjU0NTE5ZGY0YzYwZWJhNjYwNTNmNTg2NjIwYWY4YzhlYzk5NmU3ZTc). We do plan to add more and more contributors over time. Totally. But we’re taking it slow as we learn how to manage an open source project. + +#### Building + +```bash +git clone https://github.com/brentsimmons/NetNewsWire.git +cd NetNewsWire +git submodule update --init +``` + +You can locally override the Xcode settings for code signing +by creating a `DeveloperSettings.xcconfig` file locally at the appropriate path. +This allows for a pristine project with code signing set up with the appropriate +developer ID and certificates, and for dev to be able to have local settings +without needing to check in anything into source control. + +As an example, make a `../../SharedXcodeSettings/DeveloperSettings.xcconfig` file and +give it the contents + +``` +CODE_SIGN_IDENTITY = Mac Developer +DEVELOPMENT_TEAM = +CODE_SIGN_STYLE = Automatic +PROVISIONING_PROFILE_SPECIFIER = +``` + +Now you should be able to build without code signing errors and without modifying +the NetNewsWire Xcode project. + +Example: + +If your NetNewsWire Xcode project file is at: +`/Users/Shared/git/NetNewsWire/NetNewsWire.xcodeproj` + +Create your `DeveloperSettings.xcconfig` file at +`/Users/Shared/git/SharedXcodeSettings/DeveloperSettings.xcconfig` From 35c76a00cc4b92b5a5ed78418716c8b9c70af861 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 26 May 2019 05:32:02 -0500 Subject: [PATCH 002/119] Correct spelling of Feedbin. Issue #682 --- Technotes/QuestionsAnswered.md | 4 ++-- Technotes/Roadmap.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Technotes/QuestionsAnswered.md b/Technotes/QuestionsAnswered.md index 3a9b22a14..93d0e4cb9 100644 --- a/Technotes/QuestionsAnswered.md +++ b/Technotes/QuestionsAnswered.md @@ -10,8 +10,8 @@ Note: you can delete multiple feeds, and you can delete folders. You can also un Since NetNewsWire is a nights-and-weekends project, we don’t have enough time to run and test on older versions of macOS. Most of the time it will require the most recent macOS. -#### Why is FeedBin syncing planned for 1.0 but _____ isn’t planned until 2.0? +#### Why is Feedbin syncing planned for 1.0 but _____ isn’t planned until 2.0? This was a difficult decision. We didn’t want to ship with no syncing at all, but we also didn’t want to delay shipping until we’ve done a whole bunch of systems. -So we chose FeedBin, since that’s what we use, and since the folks at FeedBin have been friendly and helpful. +So we chose Feedbin, since that’s what we use, and since the folks at Feedbin have been friendly and helpful. diff --git a/Technotes/Roadmap.md b/Technotes/Roadmap.md index ebc971545..18bbcd2e9 100644 --- a/Technotes/Roadmap.md +++ b/Technotes/Roadmap.md @@ -9,7 +9,7 @@ This roadmap reflects thinking at the time of the last update. Anything can chan Features: * Standalone feed reading (unsynced) -* Syncing via FeedBin +* Syncing via Feedbin * Built-in smart feeds (today, starred, all unread) * Searching * Starring From b327d82004d18d367e531fafdc8679b490c24cee Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 26 May 2019 06:03:17 -0500 Subject: [PATCH 003/119] Update to latest RSWeb --- submodules/RSWeb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/RSWeb b/submodules/RSWeb index 09cfcb4af..59685e506 160000 --- a/submodules/RSWeb +++ b/submodules/RSWeb @@ -1 +1 @@ -Subproject commit 09cfcb4af0d63a895fdc4e4eff704e4579940334 +Subproject commit 59685e50640cd4629294bf2c0d63193ffa4ccc74 From c61949bc043615abb8431ee4f94e7afecbe7f403 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 26 May 2019 11:54:32 -0500 Subject: [PATCH 004/119] Add account name to network error messages and prevent background iOS errors from displaying alerts --- Frameworks/Account/Account.swift | 30 ++------- .../Account/Account.xcodeproj/project.pbxproj | 4 ++ Frameworks/Account/AccountDelegate.swift | 2 +- Frameworks/Account/AccountError.swift | 63 +++++++++++++++++++ Frameworks/Account/AccountManager.swift | 15 ++++- .../Feedbin/FeedbinAccountDelegate.swift | 43 ++++++------- .../LocalAccount/LocalAccountDelegate.swift | 4 +- Mac/AppDelegate.swift | 2 +- Mac/ErrorHandler.swift | 18 ++++++ .../AccountsFeedbinWindowController.swift | 9 ++- NetNewsWire.xcodeproj/project.pbxproj | 8 +++ Shared/Timer/AccountRefreshTimer.swift | 2 +- iOS/AppDelegate.swift | 6 +- iOS/ErrorHandler.swift | 25 ++++++++ iOS/MasterFeed/MasterFeedViewController.swift | 2 +- .../MasterTimelineViewController.swift | 2 +- .../FeedbinAccountViewController.swift | 11 +++- 17 files changed, 185 insertions(+), 61 deletions(-) create mode 100644 Frameworks/Account/AccountError.swift create mode 100644 Mac/ErrorHandler.swift create mode 100644 iOS/ErrorHandler.swift diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index ed6365030..aa2f57ac1 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -36,27 +36,6 @@ public enum AccountType: Int { // TODO: more } -public enum AccountError: LocalizedError { - - case createErrorNotFound - case createErrorAlreadySubscribed - case opmlImportInProgress - - public var errorDescription: String? { - switch self { - case .opmlImportInProgress: - return NSLocalizedString("An OPML import for this account is already running.", comment: "Import running") - default: - return NSLocalizedString("An unknown error occurred.", comment: "Unknown error") - } - } - - public var recoverySuggestion: String? { - return NSLocalizedString("Please try again later.", comment: "Try later") - } - -} - public final class Account: DisplayNameProvider, UnreadCountProvider, Container, Hashable { public struct UserInfoKey { @@ -309,7 +288,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } } - public func refreshAll(completion: (() -> Void)? = nil) { + public func refreshAll(completion: @escaping (Result) -> Void) { self.delegate.refreshAll(for: self, completion: completion) } @@ -334,9 +313,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, guard let self = self else { return } // Reset the last fetch date to get the article history for the added feeds. self.metadata.lastArticleFetch = nil - self.delegate.refreshAll(for: self) { - completion(.success(())) - } + self.delegate.refreshAll(for: self, completion: completion) case .failure(let error): completion(.failure(error)) } @@ -457,8 +434,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, structureDidChange() DispatchQueue.main.async { - self.refreshAll() + self.refreshAll() { result in } } + } public func updateUnreadCounts(for feeds: Set) { diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index a86e081b8..4e8d20f58 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 51D5875B227F630B00900287 /* tags_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58758227F630B00900287 /* tags_add.json */; }; 51D5875C227F630B00900287 /* tags_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58759227F630B00900287 /* tags_initial.json */; }; 51D5875E227F643C00900287 /* AccountFolderSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D5875D227F643C00900287 /* AccountFolderSyncTest.swift */; }; + 51E3EB41229AF61B00645299 /* AccountError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB40229AF61B00645299 /* AccountError.swift */; }; 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 */; }; @@ -131,6 +132,7 @@ 51D58758227F630B00900287 /* tags_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_add.json; sourceTree = ""; }; 51D58759227F630B00900287 /* tags_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_initial.json; sourceTree = ""; }; 51D5875D227F643C00900287 /* AccountFolderSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFolderSyncTest.swift; sourceTree = ""; }; + 51E3EB40229AF61B00645299 /* AccountError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountError.swift; sourceTree = ""; }; 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 = ""; }; @@ -285,6 +287,7 @@ children = ( 848935101F62486800CEBD24 /* Account.swift */, 841974241F6DDCE4006346C4 /* AccountDelegate.swift */, + 51E3EB40229AF61B00645299 /* AccountError.swift */, 846E77531F6F00E300A165E2 /* AccountManager.swift */, 84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */, 84F73CF0202788D80000BCEF /* ArticleFetcher.swift */, @@ -530,6 +533,7 @@ 84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */, 5133231122810EB200C30F19 /* FeedbinIcon.swift in Sources */, 846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */, + 51E3EB41229AF61B00645299 /* AccountError.swift in Sources */, 51E59599228C77BC00FCC42B /* FeedbinUnreadEntry.swift in Sources */, 5165D72822835F7800D9D53D /* FeedFinder.swift in Sources */, 51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */, diff --git a/Frameworks/Account/AccountDelegate.swift b/Frameworks/Account/AccountDelegate.swift index aaffefc16..314a944c5 100644 --- a/Frameworks/Account/AccountDelegate.swift +++ b/Frameworks/Account/AccountDelegate.swift @@ -22,7 +22,7 @@ protocol AccountDelegate { var refreshProgress: DownloadProgress { get } - func refreshAll(for account: Account, completion: (() -> Void)?) + func refreshAll(for account: Account, completion: @escaping (Result) -> Void) func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) func refreshArticleStatus(for account: Account, completion: @escaping (() -> Void)) diff --git a/Frameworks/Account/AccountError.swift b/Frameworks/Account/AccountError.swift new file mode 100644 index 000000000..d655a76f1 --- /dev/null +++ b/Frameworks/Account/AccountError.swift @@ -0,0 +1,63 @@ +// +// AccountError.swift +// Account +// +// Created by Maurice Parker on 5/26/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation +import RSWeb + +public enum AccountError: LocalizedError { + + case createErrorNotFound + case createErrorAlreadySubscribed + case opmlImportInProgress + case wrappedError(error: Error, account: Account) + + public var errorDescription: String? { + switch self { + case .opmlImportInProgress: + return NSLocalizedString("An OPML import for this account is already running.", comment: "Import running") + case .wrappedError(let error, let account): + switch error { + case TransportError.httpError(let status): + if status == 401 { + let localizedText = NSLocalizedString("Your \"%@\" credentials are invalid or expired.", comment: "Invalid or expired") + return NSString.localizedStringWithFormat(localizedText as NSString, account.nameForDisplay) as String + } else { + return unknownError(error, account) + } + default: + return unknownError(error, account) + } + default: + return NSLocalizedString("An unknown error occurred.", comment: "Unknown error") + } + } + + public var recoverySuggestion: String? { + switch self { + case .wrappedError(let error, _): + switch error { + case TransportError.httpError(let status): + if status == 401 { + return NSLocalizedString("Please update your credentials for this account.", comment: "Try later") + } else { + return NSLocalizedString("Please try again later.", comment: "Try later") + } + default: + return NSLocalizedString("Please try again later.", comment: "Try later") + } + default: + return NSLocalizedString("Please try again later.", comment: "Try later") + } + } + + private func unknownError(_ error: Error, _ account: Account) -> String { + let localizedText = NSLocalizedString("An error occurred while processing the \"%@\" account: %@", comment: "Unknown error") + return NSString.localizedStringWithFormat(localizedText as NSString, account.nameForDisplay, error.localizedDescription) as String + } + +} diff --git a/Frameworks/Account/AccountManager.swift b/Frameworks/Account/AccountManager.swift index 48f7de3cb..7f72d5dd9 100644 --- a/Frameworks/Account/AccountManager.swift +++ b/Frameworks/Account/AccountManager.swift @@ -21,6 +21,7 @@ public final class AccountManager: UnreadCountProvider { public static let shared = AccountManager() public let defaultAccount: Account + private let accountsFolder = RSDataSubfolder(nil, "Accounts")! private var accountsDictionary = [String: Account]() @@ -145,9 +146,19 @@ public final class AccountManager: UnreadCountProvider { return accountsDictionary[accountID] } - public func refreshAll() { + public func refreshAll(errorHandler: @escaping (Error) -> Void) { - activeAccounts.forEach { $0.refreshAll() } + activeAccounts.forEach { account in + account.refreshAll() { result in + switch result { + case .success: + break + case .failure(let error): + errorHandler(error) + } + } + } + } public func syncArticleStatusAll(completion: (() -> Void)? = nil) { diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index bf2ba57fd..1e172fde2 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -78,7 +78,7 @@ final class FeedbinAccountDelegate: AccountDelegate { var refreshProgress = DownloadProgress(numberOfTasks: 0) - func refreshAll(for account: Account, completion: (() -> Void)? = nil) { + func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(6) @@ -91,7 +91,7 @@ final class FeedbinAccountDelegate: AccountDelegate { self.refreshMissingArticles(account) { self.refreshProgress.clear() DispatchQueue.main.async { - completion?() + completion(.success(())) } } } @@ -99,9 +99,9 @@ final class FeedbinAccountDelegate: AccountDelegate { case .failure(let error): DispatchQueue.main.async { - completion?() self.refreshProgress.clear() - self.handleError(error) + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } @@ -222,7 +222,8 @@ final class FeedbinAccountDelegate: AccountDelegate { os_log(.debug, log: self.log, "Import OPML failed.") self.opmlImportInProgress = false DispatchQueue.main.async { - completion(.failure(error)) + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } } @@ -240,7 +241,8 @@ final class FeedbinAccountDelegate: AccountDelegate { } case .failure(let error): DispatchQueue.main.async { - completion(.failure(error)) + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } } @@ -274,7 +276,8 @@ final class FeedbinAccountDelegate: AccountDelegate { self.syncTaggings(account, taggings) case .failure(let error): DispatchQueue.main.async { - completion(.failure(error)) + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } } @@ -302,7 +305,8 @@ final class FeedbinAccountDelegate: AccountDelegate { } case .failure(let error): DispatchQueue.main.async { - completion(.failure(error)) + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } @@ -327,7 +331,8 @@ final class FeedbinAccountDelegate: AccountDelegate { } case .failure(let error): DispatchQueue.main.async { - completion(.failure(error)) + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } } @@ -356,7 +361,8 @@ final class FeedbinAccountDelegate: AccountDelegate { } case .failure(let error): DispatchQueue.main.async { - completion(.failure(error)) + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } } @@ -377,7 +383,8 @@ final class FeedbinAccountDelegate: AccountDelegate { } case .failure(let error): DispatchQueue.main.async { - completion(.failure(error)) + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } } @@ -404,7 +411,8 @@ final class FeedbinAccountDelegate: AccountDelegate { } case .failure(let error): DispatchQueue.main.async { - completion(.failure(error)) + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } } @@ -427,7 +435,8 @@ final class FeedbinAccountDelegate: AccountDelegate { self.processRestoredFeed(for: account, feed: feed, editedName: editedName, folder: folder, completion: completion) case .failure(let error): DispatchQueue.main.async { - completion(.failure(error)) + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } } @@ -491,14 +500,6 @@ final class FeedbinAccountDelegate: AccountDelegate { private extension FeedbinAccountDelegate { - func handleError(_ error: Error) { - #if os(macOS) - NSApplication.shared.presentError(error) - #else - UIApplication.shared.presentError(error) - #endif - } - func refreshAccount(_ account: Account, completion: @escaping (Result) -> Void) { caller.retrieveTags { result in diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 3e9a97d81..1a35a1796 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -35,9 +35,9 @@ final class LocalAccountDelegate: AccountDelegate { } // LocalAccountDelegate doesn't wait for completion before calling the completion block - func refreshAll(for account: Account, completion: (() -> Void)? = nil) { + func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { refresher.refreshFeeds(account.flattenedFeeds()) - completion?() + completion(.success(())) } func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) { diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index dc6a95206..269591996 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -334,7 +334,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, @IBAction func refreshAll(_ sender: Any?) { - AccountManager.shared.refreshAll() + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present) } @IBAction func showAddFeedWindow(_ sender: Any?) { diff --git a/Mac/ErrorHandler.swift b/Mac/ErrorHandler.swift new file mode 100644 index 000000000..f0258a7e2 --- /dev/null +++ b/Mac/ErrorHandler.swift @@ -0,0 +1,18 @@ +// +// ErrorHandler.swift +// NetNewsWire +// +// Created by Maurice Parker on 5/26/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import AppKit +import Account + +struct ErrorHandler { + + public static func present(_ error: Error) { + NSApplication.shared.presentError(error) + } + +} diff --git a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift index 33b4b8375..11cdcc109 100644 --- a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift @@ -88,7 +88,14 @@ class AccountsFeedbinWindowController: NSWindowController { try self.account?.removeBasicCredentials() try self.account?.storeCredentials(credentials) if newAccount { - self.account?.refreshAll() + self.account?.refreshAll() { result in + switch result { + case .success: + break + case .failure(let error): + NSApplication.shared.presentError(error) + } + } } self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 6f8387122..bb3af139d 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -118,6 +118,8 @@ 51C452B42265141B00C03939 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51C452B32265141B00C03939 /* WebKit.framework */; }; 51C452B82265178500C03939 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 51C452B72265178500C03939 /* styleSheet.css */; }; 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; + 51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; }; + 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB3C229AB08300645299 /* ErrorHandler.swift */; }; 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; 51E595AB228DF94C00FCC42B /* SettingsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51E595AA228DF94C00FCC42B /* SettingsTableViewCell.xib */; }; @@ -711,6 +713,8 @@ 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 = ""; }; + 51E3EB32229AB02C00645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; + 51E3EB3C229AB08300645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleStatusSyncTimer.swift; sourceTree = ""; }; 51E595AA228DF94C00FCC42B /* SettingsTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsTableViewCell.xib; sourceTree = ""; }; 51E595AC228E1C2100FCC42B /* AddAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountViewController.swift; sourceTree = ""; }; @@ -1550,6 +1554,7 @@ 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */, 849EE70E203919360082A1EA /* AppAssets.swift */, 842E45DC1ED8C54B000A8B52 /* Browser.swift */, + 51E3EB32229AB02C00645299 /* ErrorHandler.swift */, 842E45E11ED8C681000A8B52 /* MainWindow */, 84BBB12A20142A4700F054F5 /* Inspector */, 84C9FC6922629E1200D921D6 /* Preferences */, @@ -1665,6 +1670,7 @@ 840D617E2029031C009BC708 /* AppDelegate.swift */, 51C45254226507D200C03939 /* AppAssets.swift */, 51C45255226507D200C03939 /* AppDefaults.swift */, + 51E3EB3C229AB08300645299 /* ErrorHandler.swift */, 5126EE96226CB48A00C22AFC /* NavigationStateController.swift */, 51C4525D226508F600C03939 /* MasterFeed */, 51C4526D2265091600C03939 /* MasterTimeline */, @@ -2344,6 +2350,7 @@ 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */, 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */, 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */, + 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */, 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */, 51EF0F7C2277919E0050506E /* TimelineNumberOfLinesViewController.swift in Sources */, 51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */, @@ -2458,6 +2465,7 @@ 849A97791ED9EC04007D329B /* TimelineStringFormatter.swift in Sources */, 84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */, 8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */, + 51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */, 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */, 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */, 84AD1EAA2031617300BC20B7 /* FolderPasteboardWriter.swift in Sources */, diff --git a/Shared/Timer/AccountRefreshTimer.swift b/Shared/Timer/AccountRefreshTimer.swift index 91c294f20..8a5af89ba 100644 --- a/Shared/Timer/AccountRefreshTimer.swift +++ b/Shared/Timer/AccountRefreshTimer.swift @@ -73,7 +73,7 @@ class AccountRefreshTimer { lastTimedRefresh = Date() update() - AccountManager.shared.refreshAll() + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present) } diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index a13689b43..0fa232d3f 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -175,10 +175,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele // If we haven't refreshed the database for 15 minutes, run a refresh automatically if let lastRefresh = AppDefaults.lastRefresh { if Date() > lastRefresh.addingTimeInterval(15 * 60) { - AccountManager.shared.refreshAll() + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present) } } else { - AccountManager.shared.refreshAll() + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present) } } @@ -222,7 +222,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele startingUnreadCount = self.unreadCount DispatchQueue.main.async { - AccountManager.shared.refreshAll() + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) } os_log("Accounts requested to begin refresh.", log: self.log, type: .debug) diff --git a/iOS/ErrorHandler.swift b/iOS/ErrorHandler.swift new file mode 100644 index 000000000..2bedf0a69 --- /dev/null +++ b/iOS/ErrorHandler.swift @@ -0,0 +1,25 @@ +// +// ErrorHandler.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 5/26/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit +import RSCore +import os.log + +struct ErrorHandler { + + private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Account") + + public static func present(_ error: Error) { + UIApplication.shared.presentError(error) + } + + public static func log(_ error: Error) { + os_log(.error, log: self.log, "%@", error.localizedDescription) + } + +} diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index cfb6fb1c2..73a654501 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -603,7 +603,7 @@ extension MasterFeedViewController: MasterFeedTableViewCellDelegate { private extension MasterFeedViewController { @objc private func refreshAccounts(_ sender: Any) { - AccountManager.shared.refreshAll() + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present) refreshControl?.endRefreshing() } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index dc716ec7c..0ac2aa9cf 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -351,7 +351,7 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand private extension MasterTimelineViewController { @objc private func refreshAccounts(_ sender: Any) { - AccountManager.shared.refreshAll() + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present) refreshControl?.endRefreshing() } diff --git a/iOS/Settings/FeedbinAccountViewController.swift b/iOS/Settings/FeedbinAccountViewController.swift index 69c19c43e..d826c5086 100644 --- a/iOS/Settings/FeedbinAccountViewController.swift +++ b/iOS/Settings/FeedbinAccountViewController.swift @@ -72,10 +72,19 @@ class FeedbinAccountViewController: UIViewController { } do { + try self.account?.removeBasicCredentials() try self.account?.storeCredentials(credentials) + if newAccount { - self.account?.refreshAll() + self.account?.refreshAll() { result in + switch result { + case .success: + break + case .failure(let error): + UIApplication.shared.presentError(error) + } + } } self.delegate?.dismiss(self) From 32712533d15504201e95ef69043a92f529d69f1c Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 26 May 2019 12:17:38 -0500 Subject: [PATCH 005/119] Fixed duplicate adds for local account folder feed adds. Issue #701 --- Frameworks/Account/Account.swift | 2 -- Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift | 1 - Frameworks/Account/LocalAccount/LocalAccountDelegate.swift | 5 ++--- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index aa2f57ac1..4bb5df495 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -388,8 +388,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, feed.name = name feed.homePageURL = homePageURL - addFeed(feed) - return feed } diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index 1e172fde2..bb9294869 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -377,7 +377,6 @@ final class FeedbinAccountDelegate: AccountDelegate { case .success(let taggingID): DispatchQueue.main.async { self.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: String(taggingID)) - account.removeFeed(feed) folder.addFeed(feed) completion(.success(())) } diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 1a35a1796..51e2c4879 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -138,11 +138,10 @@ final class LocalAccountDelegate: AccountDelegate { } func addFeed(for account: Account, to container: Container, with feed: Feed, completion: @escaping (Result) -> Void) { - if let account = container as? Account { - account.addFeed(feed) - } if let folder = container as? Folder { folder.addFeed(feed) + } else if let account = container as? Account { + account.addFeed(feed) } completion(.success(())) } From 4de509326f787b1e09a0c6cce44fb645d23f2834 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 26 May 2019 11:55:24 -0700 Subject: [PATCH 006/119] =?UTF-8?q?Change=20OPML=20menu=20items=20to=20Imp?= =?UTF-8?q?ort=20Subscriptions=E2=80=A6=20and=20Export=20Subscriptions.=20?= =?UTF-8?q?Change=20Close=20menu=20item=20to=20Close=20Window.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mac/Base.lproj/Main.storyboard | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mac/Base.lproj/Main.storyboard b/Mac/Base.lproj/Main.storyboard index 875c10de2..83e29b174 100644 --- a/Mac/Base.lproj/Main.storyboard +++ b/Mac/Base.lproj/Main.storyboard @@ -85,20 +85,20 @@ - + - + - + From 31f8348d118d94369ab0147cd24fb6714400b891 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 26 May 2019 12:12:29 -0700 Subject: [PATCH 007/119] Add explanation text to the Name field in Account Information preferences. --- Mac/Preferences/Accounts/AccountsDetail.xib | 29 ++++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Mac/Preferences/Accounts/AccountsDetail.xib b/Mac/Preferences/Accounts/AccountsDetail.xib index 12ecf43f9..9018102d1 100644 --- a/Mac/Preferences/Accounts/AccountsDetail.xib +++ b/Mac/Preferences/Accounts/AccountsDetail.xib @@ -27,11 +27,12 @@ - + + @@ -40,7 +41,7 @@ - + @@ -50,7 +51,7 @@ - + @@ -61,7 +62,7 @@ + + + - - + + - - + + + + + + - + From 9f6b2bc63f955a0eb8f1d791a5f0619408a55424 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 26 May 2019 16:29:46 -0700 Subject: [PATCH 012/119] Revise layout and add explanation for the import OPML choose-account sheet. --- Mac/MainWindow/OPML/ImportOPMLSheet.xib | 118 ++++++++++++------------ 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/Mac/MainWindow/OPML/ImportOPMLSheet.xib b/Mac/MainWindow/OPML/ImportOPMLSheet.xib index 14b0dded9..16a910ce4 100644 --- a/Mac/MainWindow/OPML/ImportOPMLSheet.xib +++ b/Mac/MainWindow/OPML/ImportOPMLSheet.xib @@ -13,65 +13,47 @@ - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + - + - + + + - + From 7e6db47956652258011a8de7a55f372620948063 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 26 May 2019 18:35:54 -0500 Subject: [PATCH 013/119] Add extended mode for Feedbin entries to get avatar url. Issue #669 --- .../Account/Feedbin/FeedbinAPICaller.swift | 23 ++++++++++--------- .../Feedbin/FeedbinAccountDelegate.swift | 2 +- Frameworks/Account/Feedbin/FeedbinEntry.swift | 18 +++++++++++++++ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift index 86072b688..ba1e0fa94 100644 --- a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift +++ b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift @@ -358,9 +358,9 @@ final class FeedbinAPICaller: NSObject { let concatIDs = articleIDs.reduce("") { param, articleID in return param + ",\(articleID)" } let paramIDs = String(concatIDs.dropFirst()) - var callURL = URLComponents(url: feedbinBaseURL.appendingPathComponent("entries.json"), resolvingAgainstBaseURL: false)! - callURL.queryItems = [URLQueryItem(name: "ids", value: paramIDs)] - let request = URLRequest(url: callURL.url!, credentials: credentials) + var callComponents = URLComponents(url: feedbinBaseURL.appendingPathComponent("entries.json"), resolvingAgainstBaseURL: false)! + callComponents.queryItems = [URLQueryItem(name: "ids", value: paramIDs), URLQueryItem(name: "mode", value: "extended")] + let request = URLRequest(url: callComponents.url!, credentials: credentials) transport.send(request: request, resultType: [FeedbinEntry].self) { result in @@ -380,9 +380,9 @@ final class FeedbinAPICaller: NSObject { let since = Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date() let sinceString = FeedbinDate.formatter.string(from: since) - var callURL = URLComponents(url: feedbinBaseURL.appendingPathComponent("/feeds/\(feedID)/entries.json"), resolvingAgainstBaseURL: false)! - callURL.queryItems = [URLQueryItem(name: "since", value: sinceString), URLQueryItem(name: "per_page", value: "100")] - let request = URLRequest(url: callURL.url!, credentials: credentials) + var callComponents = URLComponents(url: feedbinBaseURL.appendingPathComponent("feeds/\(feedID)/entries.json"), resolvingAgainstBaseURL: false)! + callComponents.queryItems = [URLQueryItem(name: "since", value: sinceString), URLQueryItem(name: "per_page", value: "100"), URLQueryItem(name: "mode", value: "extended")] + let request = URLRequest(url: callComponents.url!, credentials: credentials) transport.send(request: request, resultType: [FeedbinEntry].self) { result in @@ -411,9 +411,9 @@ final class FeedbinAPICaller: NSObject { }() let sinceString = FeedbinDate.formatter.string(from: since) - var callURL = URLComponents(url: feedbinBaseURL.appendingPathComponent("entries.json"), resolvingAgainstBaseURL: false)! - callURL.queryItems = [URLQueryItem(name: "since", value: sinceString), URLQueryItem(name: "per_page", value: "100")] - let request = URLRequest(url: callURL.url!, credentials: credentials) + var callComponents = URLComponents(url: feedbinBaseURL.appendingPathComponent("entries.json"), resolvingAgainstBaseURL: false)! + callComponents.queryItems = [URLQueryItem(name: "since", value: sinceString), URLQueryItem(name: "per_page", value: "100"), URLQueryItem(name: "mode", value: "extended")] + let request = URLRequest(url: callComponents.url!, credentials: credentials) transport.send(request: request, resultType: [FeedbinEntry].self) { result in @@ -438,12 +438,13 @@ final class FeedbinAPICaller: NSObject { func retrieveEntries(page: String, completion: @escaping (Result<([FeedbinEntry]?, String?), Error>) -> Void) { - guard let callURL = URL(string: page) else { + guard let url = URL(string: page), var callComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { completion(.success((nil, nil))) return } - let request = URLRequest(url: callURL, credentials: credentials) + callComponents.queryItems?.append(URLQueryItem(name: "mode", value: "extended")) + let request = URLRequest(url: callComponents.url!, credentials: credentials) transport.send(request: request, resultType: [FeedbinEntry].self) { result in diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index bb9294869..065d9b487 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -1098,7 +1098,7 @@ private extension FeedbinAccountDelegate { } let parsedItems: [ParsedItem] = entries.map { entry in - let authors = Set([ParsedAuthor(name: entry.authorName, url: nil, avatarURL: nil, emailAddress: nil)]) + let authors = Set([ParsedAuthor(name: entry.authorName, url: entry.jsonFeed?.jsonFeedAuthor?.url, avatarURL: entry.jsonFeed?.jsonFeedAuthor?.avatarURL, emailAddress: nil)]) return ParsedItem(syncServiceID: String(entry.articleID), uniqueID: String(entry.articleID), feedURL: String(entry.feedID), url: nil, externalURL: entry.url, title: entry.title, contentHTML: entry.contentHTML, contentText: nil, summary: entry.summary, imageURL: nil, bannerImageURL: nil, datePublished: entry.parseDatePublished(), dateModified: nil, authors: authors, tags: nil, attachments: nil) } diff --git a/Frameworks/Account/Feedbin/FeedbinEntry.swift b/Frameworks/Account/Feedbin/FeedbinEntry.swift index 2a604766e..8d73a51da 100644 --- a/Frameworks/Account/Feedbin/FeedbinEntry.swift +++ b/Frameworks/Account/Feedbin/FeedbinEntry.swift @@ -21,6 +21,7 @@ struct FeedbinEntry: Codable { let summary: String? let datePublished: String? let dateArrived: String? + let jsonFeed: FeedbinEntryJSONFeed? enum CodingKeys: String, CodingKey { case articleID = "id" @@ -32,6 +33,7 @@ struct FeedbinEntry: Codable { case summary = "summary" case datePublished = "published" case dateArrived = "created_at" + case jsonFeed = "json_feed" } // Feedbin dates can't be decoded by the JSONDecoding 8601 decoding strategy. Feedbin @@ -47,3 +49,19 @@ struct FeedbinEntry: Codable { } } + +struct FeedbinEntryJSONFeed: Codable { + let jsonFeedAuthor: FeedbinEntryJSONFeedAuthor? + enum CodingKeys: String, CodingKey { + case jsonFeedAuthor = "author" + } +} + +struct FeedbinEntryJSONFeedAuthor: Codable { + let url: String? + let avatarURL: String? + enum CodingKeys: String, CodingKey { + case url = "url" + case avatarURL = "avatar" + } +} From 886b82f3bd9ff49e2687cd4a918918056d6db387 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 26 May 2019 21:05:05 -0500 Subject: [PATCH 014/119] Fix issue where unread and unstar statuses were getting lost if there wasn't already an article. Issue #708 --- .../Feedbin/FeedbinAccountDelegate.swift | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index 065d9b487..f1c446cba 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -1122,13 +1122,6 @@ private extension FeedbinAccountDelegate { _ = account.update(markUnreadArticles, statusKey: .read, flag: false) } - // Mark articles as read - let deltaReadArticleIDs = currentUnreadArticleIDs.subtracting(feedbinUnreadArticleIDs) - let markReadArticles = account.fetchArticles(forArticleIDs: deltaReadArticleIDs) - DispatchQueue.main.async { - _ = account.update(markReadArticles, statusKey: .read, flag: true) - } - // Save any unread statuses for articles we haven't yet received let markUnreadArticleIDs = Set(markUnreadArticles.map { $0.articleID }) let missingUnreadArticleIDs = deltaUnreadArticleIDs.subtracting(markUnreadArticleIDs) @@ -1138,6 +1131,22 @@ private extension FeedbinAccountDelegate { } } + // Mark articles as read + let deltaReadArticleIDs = currentUnreadArticleIDs.subtracting(feedbinUnreadArticleIDs) + let markReadArticles = account.fetchArticles(forArticleIDs: deltaReadArticleIDs) + DispatchQueue.main.async { + _ = account.update(markReadArticles, statusKey: .read, flag: true) + } + + // Save any read statuses for articles we haven't yet received + let markReadArticleIDs = Set(markReadArticles.map { $0.articleID }) + let missingReadArticleIDs = deltaReadArticleIDs.subtracting(markReadArticleIDs) + if !missingReadArticleIDs.isEmpty { + DispatchQueue.main.async { + account.ensureStatuses(missingReadArticleIDs, .read, true) + } + } + } func syncArticleStarredState(account: Account, articleIDs: [Int]?) { @@ -1156,13 +1165,6 @@ private extension FeedbinAccountDelegate { _ = account.update(markStarredArticles, statusKey: .starred, flag: true) } - // Mark articles as unstarred - let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(feedbinStarredArticleIDs) - let markUnstarredArticles = account.fetchArticles(forArticleIDs: deltaUnstarredArticleIDs) - DispatchQueue.main.async { - _ = account.update(markUnstarredArticles, statusKey: .starred, flag: false) - } - // Save any starred statuses for articles we haven't yet received let markStarredArticleIDs = Set(markStarredArticles.map { $0.articleID }) let missingStarredArticleIDs = deltaStarredArticleIDs.subtracting(markStarredArticleIDs) @@ -1172,6 +1174,22 @@ private extension FeedbinAccountDelegate { } } + // Mark articles as unstarred + let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(feedbinStarredArticleIDs) + let markUnstarredArticles = account.fetchArticles(forArticleIDs: deltaUnstarredArticleIDs) + DispatchQueue.main.async { + _ = account.update(markUnstarredArticles, statusKey: .starred, flag: false) + } + + // Save any unstarred statuses for articles we haven't yet received + let markUnstarredArticleIDs = Set(markUnstarredArticles.map { $0.articleID }) + let missingUnstarredArticleIDs = deltaUnstarredArticleIDs.subtracting(markUnstarredArticleIDs) + if !missingUnstarredArticleIDs.isEmpty { + DispatchQueue.main.async { + account.ensureStatuses(missingUnstarredArticleIDs, .starred, false) + } + } + } } From 9e0187ca55a15b1b76b9f6692296b89c0420c7bd Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 26 May 2019 21:38:13 -0700 Subject: [PATCH 015/119] Fix tableView frame in Preferences > Accounts sidebar. For some reason IB wants it wider than the clip view. This leads to unwanted horizontal scrolling. --- Mac/Base.lproj/Preferences.storyboard | 12 ++++++------ .../Accounts/AccountsPreferencesViewController.swift | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Mac/Base.lproj/Preferences.storyboard b/Mac/Base.lproj/Preferences.storyboard index 69ab33c6f..9f9ae0ab7 100644 --- a/Mac/Base.lproj/Preferences.storyboard +++ b/Mac/Base.lproj/Preferences.storyboard @@ -257,20 +257,20 @@ - + - - + + - + @@ -284,7 +284,7 @@ - + @@ -293,7 +293,7 @@ - + diff --git a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift index 8cf4cde31..4de8f17af 100644 --- a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift +++ b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift @@ -29,6 +29,10 @@ final class AccountsPreferencesViewController: NSViewController { showController(AccountsAddViewController()) + // Fix tableView frame — for some reason IB wants it 1pt wider than the clip view. This leads to unwanted horizontal scrolling. + var rTable = tableView.frame + rTable.size.width = tableView.superview!.frame.size.width + tableView.frame = rTable } @IBAction func addAccount(_ sender: Any) { From 6cd8f4259736a4735a9103bd84fa523e62aa9cf3 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 26 May 2019 21:59:41 -0700 Subject: [PATCH 016/119] Add layout constraints for the table cells in Account > Preferences sidebar. --- Mac/Base.lproj/Preferences.storyboard | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Mac/Base.lproj/Preferences.storyboard b/Mac/Base.lproj/Preferences.storyboard index 9f9ae0ab7..ef3c88eb7 100644 --- a/Mac/Base.lproj/Preferences.storyboard +++ b/Mac/Base.lproj/Preferences.storyboard @@ -257,13 +257,13 @@ - + - + @@ -284,17 +284,19 @@ - + - + - + + + + - - - + + @@ -302,6 +304,13 @@ + + + + + + + From 8322ea4f311868215c0850c986a082f4cdf68a9f Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 26 May 2019 22:01:50 -0700 Subject: [PATCH 017/119] Tweak the explanotext for the Name field in Account > Preferences. --- Mac/Preferences/Accounts/AccountsDetail.xib | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Mac/Preferences/Accounts/AccountsDetail.xib b/Mac/Preferences/Accounts/AccountsDetail.xib index 9018102d1..e7c20ea87 100644 --- a/Mac/Preferences/Accounts/AccountsDetail.xib +++ b/Mac/Preferences/Accounts/AccountsDetail.xib @@ -27,7 +27,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -51,7 +51,7 @@ - + @@ -62,7 +62,7 @@