diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index 43f9e9d63..ba985cb01 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -250,7 +250,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return delegate.refreshProgress } - init?(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) { + init(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) { switch type { case .onMyMac: self.delegate = LocalAccountDelegate() diff --git a/Account/Sources/Account/AccountManager.swift b/Account/Sources/Account/AccountManager.swift index 3f517cf6d..08adff76d 100644 --- a/Account/Sources/Account/AccountManager.swift +++ b/Account/Sources/Account/AccountManager.swift @@ -104,7 +104,7 @@ public final class AccountManager: UnreadCountProvider { abort() } - defaultAccount = Account(dataFolder: localAccountFolder, type: .onMyMac, accountID: defaultAccountIdentifier)! + defaultAccount = Account(dataFolder: localAccountFolder, type: .onMyMac, accountID: defaultAccountIdentifier) accountsDictionary[defaultAccount.accountID] = defaultAccount readAccountsFromDisk() @@ -131,7 +131,7 @@ public final class AccountManager: UnreadCountProvider { abort() } - let account = Account(dataFolder: accountFolder, type: type, accountID: accountID)! + let account = Account(dataFolder: accountFolder, type: type, accountID: accountID) accountsDictionary[accountID] = account var userInfo = [String: Any]() @@ -166,6 +166,18 @@ public final class AccountManager: UnreadCountProvider { NotificationCenter.default.post(name: .UserDidDeleteAccount, object: self, userInfo: userInfo) } + public func duplicateServiceAccount(type: AccountType, username: String?) -> Bool { + guard type != .onMyMac else { + return false + } + for account in accounts { + if account.type == type && username == account.username { + return true + } + } + return false + } + public func existingAccount(with accountID: String) -> Account? { return accountsDictionary[accountID] } @@ -423,16 +435,24 @@ private extension AccountManager { print("Error reading Accounts folder: \(error)") return } + + filenames = filenames?.sorted() filenames?.forEach { (oneFilename) in guard oneFilename != defaultAccountFolderName else { return } if let oneAccount = loadAccount(oneFilename) { - accountsDictionary[oneAccount.accountID] = oneAccount + if !duplicateServiceAccount(oneAccount) { + accountsDictionary[oneAccount.accountID] = oneAccount + } } } } + + func duplicateServiceAccount(_ account: Account) -> Bool { + return duplicateServiceAccount(type: account.type, username: account.username) + } func sortByName(_ accounts: [Account]) -> [Account] { // LocalAccount is first. diff --git a/Account/Sources/Account/Feed.swift b/Account/Sources/Account/Feed.swift index 4a14a43af..958b25106 100644 --- a/Account/Sources/Account/Feed.swift +++ b/Account/Sources/Account/Feed.swift @@ -20,3 +20,19 @@ public protocol Feed: FeedIdentifiable, ArticleFetcher, DisplayNameProvider, Unr var defaultReadFilterType: ReadFilterType { get } } + +public extension Feed { + + func readFiltered(readFilterEnabledTable: [FeedIdentifier: Bool]) -> Bool { + guard defaultReadFilterType != .alwaysRead else { + return true + } + if let feedID = feedID, let readFilterEnabled = readFilterEnabledTable[feedID] { + return readFilterEnabled + } else { + return defaultReadFilterType == .read + } + + } + +} diff --git a/Account/Sources/Account/Feedly/FeedlyAPICaller.swift b/Account/Sources/Account/Feedly/FeedlyAPICaller.swift index 5ae2cbc2f..36f974835 100644 --- a/Account/Sources/Account/Feedly/FeedlyAPICaller.swift +++ b/Account/Sources/Account/Feedly/FeedlyAPICaller.swift @@ -48,10 +48,15 @@ final class FeedlyAPICaller { private let transport: Transport private let baseUrlComponents: URLComponents + private let uriComponentAllowed: CharacterSet init(transport: Transport, api: API) { self.transport = transport self.baseUrlComponents = api.baseUrlComponents + + var urlHostAllowed = CharacterSet.urlHostAllowed + urlHostAllowed.remove("+") + uriComponentAllowed = urlHostAllowed } weak var delegate: FeedlyAPICallerDelegate? @@ -272,7 +277,7 @@ final class FeedlyAPICaller { } private func encodeForURLPath(_ pathComponent: String) -> String? { - return pathComponent.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) + return pathComponent.addingPercentEncoding(withAllowedCharacters: uriComponentAllowed) } func deleteCollection(with id: String, completion: @escaping (Result) -> ()) { diff --git a/Account/Sources/Account/Feedly/OAuthAccountAuthorizationOperation.swift b/Account/Sources/Account/Feedly/OAuthAccountAuthorizationOperation.swift index 1b78797da..4378bedc5 100644 --- a/Account/Sources/Account/Feedly/OAuthAccountAuthorizationOperation.swift +++ b/Account/Sources/Account/Feedly/OAuthAccountAuthorizationOperation.swift @@ -15,6 +15,13 @@ public protocol OAuthAccountAuthorizationOperationDelegate: class { func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) } +public enum OAuthAccountAuthorizationOperationError: LocalizedError { + case duplicateAccount + + public var errorDescription: String? { + return NSLocalizedString("There is already a Feedly account with that username created.", comment: "Duplicate Error") + } +} @objc public final class OAuthAccountAuthorizationOperation: NSObject, MainThreadOperation, ASWebAuthenticationPresentationContextProviding { public var isCanceled: Bool = false { @@ -64,10 +71,26 @@ public protocol OAuthAccountAuthorizationOperationDelegate: class { self?.didEndAuthentication(url: url, error: error) } } - self.session = session + session.presentationContextProvider = self - session.start() + guard session.start() else { + + /// Documentation does not say on why `ASWebAuthenticationSession.start` or `canStart` might return false. + /// Perhaps it has something to do with an inter-process communication failure? No browsers installed? No browsers that support web authentication? + struct UnableToStartASWebAuthenticationSessionError: LocalizedError { + let errorDescription: String? = NSLocalizedString("Unable to start a web authentication session with the default web browser.", + comment: "OAuth - error description - unable to authorize because ASWebAuthenticationSession did not start.") + let recoverySuggestion: String? = NSLocalizedString("Check your default web browser in System Preferences or change it to Safari and try again.", + comment: "OAuth - recovery suggestion - ensure browser selected supports web authentication.") + } + + didFinish(UnableToStartASWebAuthenticationSessionError()) + + return + } + + self.session = session } public func cancel() { @@ -122,7 +145,11 @@ public protocol OAuthAccountAuthorizationOperationDelegate: class { } private func saveAccount(for grant: OAuthAuthorizationGrant) { - // TODO: Find an already existing account for this username? + guard !AccountManager.shared.duplicateServiceAccount(type: .feedly, username: grant.accessToken.username) else { + didFinish(OAuthAccountAuthorizationOperationError.duplicateAccount) + return + } + let account = AccountManager.shared.createAccount(type: .feedly) do { diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift index 200554ed3..b1a549607 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift @@ -67,6 +67,13 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl addCompletionHandler = nil super.didCancel() } + + override func didFinish(with error: Error) { + assert(Thread.isMainThread) + addCompletionHandler?(.failure(error)) + addCompletionHandler = nil + super.didFinish(with: error) + } func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) { guard !isCanceled else { diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyGetCollectionsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyGetCollectionsOperation.swift index 297bd3cd7..8477bb910 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyGetCollectionsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyGetCollectionsOperation.swift @@ -32,7 +32,7 @@ final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollectionProv service.getCollections { result in switch result { case .success(let collections): - os_log(.debug, log: self.log, "Received collections: %@.", collections.map { $0.id }) + os_log(.debug, log: self.log, "Received collections: %{public}@", collections.map { $0.id }) self.collections = collections self.didFinish() diff --git a/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift index 47625992d..ef1770967 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift @@ -51,13 +51,13 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD } override func didCancel() { - os_log(.debug, log: log, "Canceling sync stream contents") + os_log(.debug, log: log, "Canceling sync stream contents for %{public}@", resource.id) operationQueue.cancelAllOperations() super.didCancel() } func enqueueOperations(for continuation: String?) { - os_log(.debug, log: log, "Requesting page for %@", resource.id) + os_log(.debug, log: log, "Requesting page for %{public}@", resource.id) let operations = pageOperations(for: continuation) operationQueue.addOperations(operations) } @@ -91,14 +91,14 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) { guard !isCanceled else { - os_log(.debug, log: log, "Cancelled requesting page for %@", resource.id) + os_log(.debug, log: log, "Cancelled requesting page for %{public}@", resource.id) return } - os_log(.debug, log: log, "Ingesting %i items from %@", stream.items.count, stream.id) + os_log(.debug, log: log, "Ingesting %i items from %{public}@", stream.items.count, stream.id) guard isPagingEnabled, let continuation = stream.continuation else { - os_log(.debug, log: log, "Reached end of stream for %@", stream.id) + os_log(.debug, log: log, "Reached end of stream for %{public}@", stream.id) return } @@ -106,7 +106,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD } func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) { - os_log(.debug, log: log, "Completed ingesting items from %@", resource.id) + os_log(.debug, log: log, "Completed ingesting items from %{public}@", resource.id) didFinish() } diff --git a/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift b/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift index cc39bf82e..3fb364410 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift @@ -135,16 +135,6 @@ extension LocalAccountRefresher: DownloadSessionDelegate { return false } - if data.count > 4096 { - let parserData = ParserData(url: feed.url, data: data) - if FeedParser.mightBeAbleToParseBasedOnPartialData(parserData) { - return true - } else { - delegate?.localAccountRefresher(self, requestCompletedFor: feed) - return false - } - } - return true } diff --git a/Frameworks/SyncDatabase/Info.plist b/Frameworks/SyncDatabase/Info.plist index 76da86ac6..5550efc84 100644 --- a/Frameworks/SyncDatabase/Info.plist +++ b/Frameworks/SyncDatabase/Info.plist @@ -19,6 +19,6 @@ CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright - Copyright © 2019 Ranchero Software. All rights reserved. + Copyright © 2019-2020 Brent Simmons. All rights reserved. diff --git a/Mac/AppDefaults.swift b/Mac/AppDefaults.swift index 278a53525..caed0f08c 100644 --- a/Mac/AppDefaults.swift +++ b/Mac/AppDefaults.swift @@ -194,7 +194,12 @@ final class AppDefaults { } var hideDockUnreadCount: Bool { - return AppDefaults.bool(for: Key.hideDockUnreadCount) + get { + return AppDefaults.bool(for: Key.hideDockUnreadCount) + } + set { + AppDefaults.setBool(for: Key.hideDockUnreadCount, newValue) + } } #if !MAC_APP_STORE diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 11089f18b..713480a8f 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -240,6 +240,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, refreshTimer = AccountRefreshTimer() syncTimer = ArticleStatusSyncTimer() + UNUserNotificationCenter.current().requestAuthorization(options:[.badge]) { (granted, error) in } + UNUserNotificationCenter.current().getNotificationSettings { (settings) in if settings.authorizationStatus == .authorized { DispatchQueue.main.async { @@ -620,7 +622,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, @IBAction func showHelp(_ sender: Any?) { - Browser.open("https://ranchero.com/netnewswire/help/mac/5.0/en/", inBackground: false) + Browser.open("https://ranchero.com/netnewswire/help/mac/5.1/en/", inBackground: false) } @IBAction func donateToAppCampForGirls(_ sender: Any?) { diff --git a/Mac/Base.lproj/MainWindow.storyboard b/Mac/Base.lproj/MainWindow.storyboard index 02c9142a1..55fc78c05 100644 --- a/Mac/Base.lproj/MainWindow.storyboard +++ b/Mac/Base.lproj/MainWindow.storyboard @@ -328,7 +328,7 @@ - + @@ -346,7 +346,7 @@ - + @@ -505,7 +505,7 @@ - + @@ -133,91 +133,81 @@ - - + + - - - + + + + + + + + + + + - + - + + + - + - - - - - - - - - - - - + + - + - + + - - - + + + + + + - - + - - + - - - + + - + @@ -230,13 +220,14 @@ - + + - + @@ -307,7 +298,7 @@ - + - + @@ -118,7 +118,7 @@ Field - + @@ -132,6 +132,7 @@ Field + @@ -156,11 +157,11 @@ Field - + - + @@ -168,7 +169,7 @@ Field - + @@ -207,11 +208,11 @@ Field - + - + @@ -219,7 +220,7 @@ Field - + @@ -249,11 +250,11 @@ Field - + - + @@ -261,7 +262,7 @@ Field - + diff --git a/Mac/Inspector/InspectorWindowController.swift b/Mac/Inspector/InspectorWindowController.swift index 01e476bc0..8f4f82e8f 100644 --- a/Mac/Inspector/InspectorWindowController.swift +++ b/Mac/Inspector/InspectorWindowController.swift @@ -12,6 +12,7 @@ protocol Inspector: class { var objects: [Any]? { get set } var isFallbackInspector: Bool { get } // Can handle nothing-to-inspect or unexpected type of objects. + var windowTitle: String { get } func canInspect(_ objects: [Any]) -> Bool } @@ -62,6 +63,7 @@ final class InspectorWindowController: NSWindowController { inspectors = [feedInspector, folderInspector, builtinSmartFeedInspector, nothingInspector] currentInspector = nothingInspector + window?.title = currentInspector.windowTitle if let savedOrigin = originFromDefaults() { window?.setFlippedOriginAdjustingForScreen(savedOrigin) @@ -108,7 +110,11 @@ private extension InspectorWindowController { guard let window = window else { return } - + + DispatchQueue.main.async { + window.title = inspector.windowTitle + } + let flippedOrigin = window.flippedOrigin if window.contentViewController != inspector { diff --git a/Mac/Inspector/NothingInspectorViewController.swift b/Mac/Inspector/NothingInspectorViewController.swift index f6ffb97f3..376e6a594 100644 --- a/Mac/Inspector/NothingInspectorViewController.swift +++ b/Mac/Inspector/NothingInspectorViewController.swift @@ -19,6 +19,7 @@ final class NothingInspectorViewController: NSViewController, Inspector { updateTextFields() } } + var windowTitle: String = NSLocalizedString("Inspector", comment: "Inspector window title") func canInspect(_ objects: [Any]) -> Bool { diff --git a/Mac/Inspector/WebFeedInspectorViewController.swift b/Mac/Inspector/WebFeedInspectorViewController.swift index ee4779a0d..c1586f05a 100644 --- a/Mac/Inspector/WebFeedInspectorViewController.swift +++ b/Mac/Inspector/WebFeedInspectorViewController.swift @@ -38,6 +38,7 @@ final class WebFeedInspectorViewController: NSViewController, Inspector { updateFeed() } } + var windowTitle: String = NSLocalizedString("Feed Inspector", comment: "Feed Inspector window title") func canInspect(_ objects: [Any]) -> Bool { return objects.count == 1 && objects.first is WebFeed @@ -112,6 +113,7 @@ extension WebFeedInspectorViewController: NSTextFieldDelegate { return } feed.editedName = nameTextField.stringValue + windowTitle = feed.editedName ?? NSLocalizedString("Feed Inspector", comment: "Feed Inspector window title") } } @@ -133,7 +135,7 @@ private extension WebFeedInspectorViewController { updateFeedURL() updateNotifyAboutNewArticles() updateIsReaderViewAlwaysOn() - + windowTitle = feed?.nameForDisplay ?? NSLocalizedString("Feed Inspector", comment: "Feed Inspector window title") view.needsLayout = true } @@ -202,7 +204,7 @@ private extension WebFeedInspectorViewController { updateAlert.addButton(withTitle: NSLocalizedString("Close", comment: "Close")) let modalResponse = updateAlert.runModal() if modalResponse == .alertFirstButtonReturn { - NSWorkspace.shared.open(URL(fileURLWithPath: "x-apple.systempreferences:com.apple.preference.notifications")) + NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.notifications")!) } } diff --git a/Mac/MainWindow/Detail/styleSheet.css b/Mac/MainWindow/Detail/styleSheet.css index 692996a7c..2caaa30eb 100644 --- a/Mac/MainWindow/Detail/styleSheet.css +++ b/Mac/MainWindow/Detail/styleSheet.css @@ -10,8 +10,8 @@ body { :root { --body-color: #444; --body-background-color: -apple-system-text-background; - --accent-color: rgb(8, 106, 238, 1); - --block-quote-border-color: rgb(8, 106, 238, .50); + --accent-color: rgba(8, 106, 238, 1); + --block-quote-border-color: rgba(8, 106, 238, .50); } @media(prefers-color-scheme: dark) { diff --git a/Mac/MainWindow/LegacyArticleExtractorButton.swift b/Mac/MainWindow/LegacyArticleExtractorButton.swift index 30fd0620d..92d5d5f2a 100644 --- a/Mac/MainWindow/LegacyArticleExtractorButton.swift +++ b/Mac/MainWindow/LegacyArticleExtractorButton.swift @@ -74,7 +74,7 @@ class LegacyArticleExtractorButton: NSButton { private func makeLayerForImage(_ image: NSImage) -> CALayer { let imageLayer = CALayer() imageLayer.bounds = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height) - imageLayer.position = CGPoint(x: bounds.midX, y: bounds.midY) + imageLayer.position = CGPoint(x: bounds.midX, y: floor(bounds.midY)) return imageLayer } @@ -90,17 +90,17 @@ class LegacyArticleExtractorButton: NSButton { let imageProgress2 = AppAssets.legacyArticleExtractorProgress2 let imageProgress3 = AppAssets.legacyArticleExtractorProgress3 let imageProgress4 = AppAssets.legacyArticleExtractorProgress4 - let images = [imageProgress1, imageProgress2, imageProgress3, imageProgress4, imageProgress3, imageProgress2] + let images = [imageProgress1, imageProgress2, imageProgress3, imageProgress4, imageProgress3, imageProgress2, imageProgress1] let imageLayer = CALayer() imageLayer.bounds = CGRect(x: 0, y: 0, width: imageProgress1?.size.width ?? 0, height: imageProgress1?.size.height ?? 0) - imageLayer.position = CGPoint(x: bounds.midX, y: bounds.midY) + imageLayer.position = CGPoint(x: bounds.midX, y: floor(bounds.midY)) hostedLayer.addSublayer(imageLayer) let animation = CAKeyframeAnimation(keyPath: "contents") animation.calculationMode = CAAnimationCalculationMode.linear - animation.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1] + animation.keyTimes = [0, 0.16, 0.32, 0.50, 0.66, 0.82, 1] animation.duration = 2 animation.values = images as [Any] animation.repeatCount = HUGE diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 9bdff6183..1bb206d19 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -295,6 +295,9 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } @IBAction func openInBrowser(_ sender: Any?) { + if AppDefaults.shared.openInBrowserInBackground { + window?.makeKeyAndOrderFront(self) + } openArticleInBrowser(sender) } @@ -304,6 +307,9 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } @IBAction func openInBrowserUsingOppositeOfSettings(_ sender: Any?) { + if !AppDefaults.shared.openInBrowserInBackground { + window?.makeKeyAndOrderFront(self) + } if let link = currentLink { Browser.open(link, inBackground: !AppDefaults.shared.openInBrowserInBackground) } @@ -413,7 +419,11 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { sidebarViewController?.focus() } } - + + @IBAction func markOlderArticlesAsRead(_ sender: Any?) { + currentTimelineViewController?.markOlderArticlesRead() + } + @IBAction func markAboveArticlesAsRead(_ sender: Any?) { currentTimelineViewController?.markAboveArticlesRead() } diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift index 2f5223d14..06d32439f 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift @@ -48,10 +48,10 @@ struct TimelineCellAppearance: Equatable { let smallItemFontSize = floor(actualFontSize * 0.90) let largeItemFontSize = actualFontSize - self.feedNameFont = NSFont.systemFont(ofSize: smallItemFontSize) + self.feedNameFont = NSFont.systemFont(ofSize: smallItemFontSize, weight: NSFont.Weight.bold) self.dateFont = NSFont.systemFont(ofSize: smallItemFontSize, weight: NSFont.Weight.bold) self.titleFont = NSFont.systemFont(ofSize: largeItemFontSize, weight: NSFont.Weight.semibold) - self.textFont = NSFont.systemFont(ofSize: largeItemFontSize, weight: NSFont.Weight.light) + self.textFont = NSFont.systemFont(ofSize: largeItemFontSize) self.textOnlyFont = NSFont.systemFont(ofSize: largeItemFontSize) self.showIcon = showIcon diff --git a/Mac/MainWindow/Timeline/TimelineTableRowView.swift b/Mac/MainWindow/Timeline/TimelineTableRowView.swift index aafd39689..e7a78d6a2 100644 --- a/Mac/MainWindow/Timeline/TimelineTableRowView.swift +++ b/Mac/MainWindow/Timeline/TimelineTableRowView.swift @@ -34,6 +34,21 @@ class TimelineTableRowView : NSTableRowView { super.init(coder: coder) } + override func drawBackground(in dirtyRect: NSRect) { + NSColor.controlBackgroundColor.setFill() + dirtyRect.fill() + } + + override func drawSelection(in dirtyRect: NSRect) { + if isEmphasized { + NSColor.selectedContentBackgroundColor.setFill() + dirtyRect.fill() + } else { + NSColor.unemphasizedSelectedContentBackgroundColor.setFill() + dirtyRect.fill() + } + } + private var cellView: TimelineTableCellView? { for oneSubview in subviews { if let foundView = oneSubview as? TimelineTableCellView { diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index a73a34495..aa6c99766 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -44,11 +44,20 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } var isCleanUpAvailable: Bool { - guard isReadFiltered ?? false else { return false } + let isEligibleForCleanUp: Bool? + + if representedObjects?.count == 1, let timelineFeed = representedObjects?.first as? Feed, timelineFeed.defaultReadFilterType == .alwaysRead { + isEligibleForCleanUp = true + } else { + isEligibleForCleanUp = isReadFiltered + } + + guard isEligibleForCleanUp ?? false else { return false } let readSelectedCount = selectedArticles.filter({ $0.status.read }).count let readArticleCount = articles.count - unreadCount let availableToCleanCount = readArticleCount - readSelectedCount + return availableToCleanCount > 0 } @@ -451,6 +460,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr return .canDoNothing } + func markOlderArticlesRead() { + markOlderArticlesRead(selectedArticles) + } + func markAboveArticlesRead() { markAboveArticlesRead(selectedArticles) } @@ -469,6 +482,33 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr return articles.articlesBelow(article: last).canMarkAllAsRead() } + func markOlderArticlesRead(_ selectedArticles: [Article]) { + // Mark articles older than the selectedArticles(s) as read. + + var cutoffDate: Date? = nil + for article in selectedArticles { + if cutoffDate == nil { + cutoffDate = article.logicalDatePublished + } + else if cutoffDate! > article.logicalDatePublished { + cutoffDate = article.logicalDatePublished + } + } + if cutoffDate == nil { + return + } + + let articlesToMark = articles.filter { $0.logicalDatePublished < cutoffDate! } + if articlesToMark.isEmpty { + return + } + + guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else { + return + } + runCommand(markReadCommand) + } + func markAboveArticlesRead(_ selectedArticles: [Article]) { guard let first = selectedArticles.first else { return } let articlesToMark = articles.articlesAbove(article: first) @@ -1129,19 +1169,19 @@ private extension TimelineViewController { func fetchUnsortedArticlesSync(for representedObjects: [Any]) -> Set
{ cancelPendingAsyncFetches() - let articleFetchers = representedObjects.compactMap{ $0 as? ArticleFetcher } - if articleFetchers.isEmpty { + let fetchers = representedObjects.compactMap{ $0 as? ArticleFetcher } + if fetchers.isEmpty { return Set
() } var fetchedArticles = Set
() - for articleFetcher in articleFetchers { - if isReadFiltered ?? true { - if let articles = try? articleFetcher.fetchUnreadArticles() { + for fetchers in fetchers { + if (fetchers as? Feed)?.readFiltered(readFilterEnabledTable: readFilterEnabledTable) ?? true { + if let articles = try? fetchers.fetchUnreadArticles() { fetchedArticles.formUnion(articles) } } else { - if let articles = try? articleFetcher.fetchArticles() { + if let articles = try? fetchers.fetchArticles() { fetchedArticles.formUnion(articles) } } @@ -1154,7 +1194,8 @@ private extension TimelineViewController { // if it’s been superseded by a newer fetch, or the timeline was emptied, etc., it won’t get called. precondition(Thread.isMainThread) cancelPendingAsyncFetches() - let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: isReadFiltered ?? true, representedObjects: representedObjects) { [weak self] (articles, operation) in + let fetchers = representedObjects.compactMap { $0 as? ArticleFetcher } + let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilterEnabledTable: readFilterEnabledTable, fetchers: fetchers) { [weak self] (articles, operation) in precondition(Thread.isMainThread) guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else { return diff --git a/Mac/Preferences/Accounts/AccountsAdd.xib b/Mac/Preferences/Accounts/AccountsAdd.xib index 6ed60e9fd..d95f1f842 100644 --- a/Mac/Preferences/Accounts/AccountsAdd.xib +++ b/Mac/Preferences/Accounts/AccountsAdd.xib @@ -1,7 +1,7 @@ - + - + @@ -13,19 +13,19 @@ - - + + - + - + - - + + @@ -33,7 +33,6 @@ - @@ -49,7 +48,7 @@ - + @@ -60,7 +59,7 @@ - + @@ -77,10 +76,24 @@ + + + + + @@ -109,6 +122,7 @@ + diff --git a/Mac/Preferences/Accounts/AccountsAddTableCellView.swift b/Mac/Preferences/Accounts/AccountsAddTableCellView.swift index cf8af80ec..fca762320 100644 --- a/Mac/Preferences/Accounts/AccountsAddTableCellView.swift +++ b/Mac/Preferences/Accounts/AccountsAddTableCellView.swift @@ -7,10 +7,23 @@ // import AppKit +import Account + +protocol AccountsAddTableCellViewDelegate: class { + func addAccount(_ accountType: AccountType) +} class AccountsAddTableCellView: NSTableCellView { + weak var delegate: AccountsAddTableCellViewDelegate? + var accountType: AccountType? + @IBOutlet weak var accountImageView: NSImageView? @IBOutlet weak var accountNameLabel: NSTextField? + @IBAction func pressed(_ sender: Any) { + guard let accountType = accountType else { return } + delegate?.addAccount(accountType) + } + } diff --git a/Mac/Preferences/Accounts/AccountsAddViewController.swift b/Mac/Preferences/Accounts/AccountsAddViewController.swift index 92d07d12e..60c52ed01 100644 --- a/Mac/Preferences/Accounts/AccountsAddViewController.swift +++ b/Mac/Preferences/Accounts/AccountsAddViewController.swift @@ -61,6 +61,10 @@ extension AccountsAddViewController: NSTableViewDelegate { func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? AccountsAddTableCellView { + + cell.accountType = addableAccountTypes[row] + cell.delegate = self + switch addableAccountTypes[row] { case .onMyMac: cell.accountNameLabel?.stringValue = Account.defaultLocalAccountName @@ -88,19 +92,21 @@ extension AccountsAddViewController: NSTableViewDelegate { } return nil } - - func tableViewSelectionDidChange(_ notification: Notification) { - let selectedRow = tableView.selectedRow - guard selectedRow != -1 else { - return - } +} - switch addableAccountTypes[selectedRow] { +// MARK: AccountsAddTableCellViewDelegate + +extension AccountsAddViewController: AccountsAddTableCellViewDelegate { + + func addAccount(_ accountType: AccountType) { + + switch accountType { case .onMyMac: let accountsAddLocalWindowController = AccountsAddLocalWindowController() accountsAddLocalWindowController.runSheetOnWindow(self.view.window!) accountsAddWindowController = accountsAddLocalWindowController + case .cloudKit: let accountsAddCloudKitWindowController = AccountsAddCloudKitWindowController() accountsAddCloudKitWindowController.runSheetOnWindow(self.view.window!) { response in @@ -110,34 +116,66 @@ extension AccountsAddViewController: NSTableViewDelegate { } } accountsAddWindowController = accountsAddCloudKitWindowController + case .feedbin: let accountsFeedbinWindowController = AccountsFeedbinWindowController() accountsFeedbinWindowController.runSheetOnWindow(self.view.window!) accountsAddWindowController = accountsFeedbinWindowController + case .feedWrangler: let accountsFeedWranglerWindowController = AccountsFeedWranglerWindowController() accountsFeedWranglerWindowController.runSheetOnWindow(self.view.window!) accountsAddWindowController = accountsFeedWranglerWindowController + case .freshRSS: let accountsReaderAPIWindowController = AccountsReaderAPIWindowController() accountsReaderAPIWindowController.accountType = .freshRSS accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) accountsAddWindowController = accountsReaderAPIWindowController + case .feedly: let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly) addAccount.delegate = self addAccount.presentationAnchor = self.view.window! + + runAwaitingFeedlyLoginAlertModal(forLifetimeOf: addAccount) + MainThreadOperationQueue.shared.add(addAccount) + case .newsBlur: let accountsNewsBlurWindowController = AccountsNewsBlurWindowController() accountsNewsBlurWindowController.runSheetOnWindow(self.view.window!) accountsAddWindowController = accountsNewsBlurWindowController } - tableView.selectRowIndexes([], byExtendingSelection: false) - } + private func runAwaitingFeedlyLoginAlertModal(forLifetimeOf operation: OAuthAccountAuthorizationOperation) { + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = NSLocalizedString("Waiting for access to Feedly", + comment: "Alert title when adding a Feedly account and waiting for authorization from the user.") + + alert.informativeText = NSLocalizedString("Your default web browser will open the Feedly login for you to authorize access.", + comment: "Alert informative text when adding a Feedly account and waiting for authorization from the user.") + + alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel")) + + let attachedWindow = self.view.window! + + alert.beginSheetModal(for: attachedWindow) { response in + if response == .alertFirstButtonReturn { + operation.cancel() + } + } + + operation.completionBlock = { _ in + guard alert.window.isVisible else { + return + } + attachedWindow.endSheet(alert.window) + } + } } // MARK: OAuthAccountAuthorizationOperationDelegate @@ -145,6 +183,12 @@ extension AccountsAddViewController: NSTableViewDelegate { extension AccountsAddViewController: OAuthAccountAuthorizationOperationDelegate { func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { + // `OAuthAccountAuthorizationOperation` is using `ASWebAuthenticationSession` which bounces the user + // to their browser on macOS for authorizing NetNewsWire to access the user's Feedly account. + // When this authorization is granted, the browser remains the foreground app which is unfortunate + // because the user probably wants to see the result of authorizing NetNewsWire to act on their behalf. + NSApp.activate(ignoringOtherApps: true) + account.refreshAll { [weak self] result in switch result { case .success: @@ -156,6 +200,10 @@ extension AccountsAddViewController: OAuthAccountAuthorizationOperationDelegate } func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { + // `OAuthAccountAuthorizationOperation` is using `ASWebAuthenticationSession` which bounces the user + // to their browser on macOS for authorizing NetNewsWire to access the user's Feedly account. + NSApp.activate(ignoringOtherApps: true) + view.window?.presentError(error) } } diff --git a/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift index 22eaf0097..5f2e2081b 100644 --- a/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift @@ -56,6 +56,11 @@ class AccountsFeedWranglerWindowController: NSWindowController { return } + guard !AccountManager.shared.duplicateServiceAccount(type: .feedWrangler, username: usernameTextField.stringValue) else { + self.errorMessageLabel.stringValue = NSLocalizedString("There is already a FeedWrangler account with that username created.", comment: "Duplicate Error") + return + } + actionButton.isEnabled = false progressIndicator.isHidden = false progressIndicator.startAnimation(self) diff --git a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift index 711ab2758..e6598cd5a 100644 --- a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift @@ -58,6 +58,11 @@ class AccountsFeedbinWindowController: NSWindowController { return } + guard !AccountManager.shared.duplicateServiceAccount(type: .feedbin, username: usernameTextField.stringValue) else { + self.errorMessageLabel.stringValue = NSLocalizedString("There is already a Feedbin account with that username created.", comment: "Duplicate Error") + return + } + actionButton.isEnabled = false progressIndicator.isHidden = false progressIndicator.startAnimation(self) diff --git a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift index d1b1a2bb8..723e254d1 100644 --- a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift @@ -55,7 +55,12 @@ class AccountsNewsBlurWindowController: NSWindowController { self.errorMessageLabel.stringValue = NSLocalizedString("Username required.", comment: "Credentials Error") return } - + + guard !AccountManager.shared.duplicateServiceAccount(type: .newsBlur, username: usernameTextField.stringValue) else { + self.errorMessageLabel.stringValue = NSLocalizedString("There is already a NewsBlur account with that username created.", comment: "Duplicate Error") + return + } + actionButton.isEnabled = false progressIndicator.isHidden = false progressIndicator.startAnimation(self) diff --git a/Mac/Preferences/General/GeneralPrefencesViewController.swift b/Mac/Preferences/General/GeneralPrefencesViewController.swift index 2d3c1d765..1ecfb3ff3 100644 --- a/Mac/Preferences/General/GeneralPrefencesViewController.swift +++ b/Mac/Preferences/General/GeneralPrefencesViewController.swift @@ -9,11 +9,15 @@ import AppKit import RSCore import RSWeb +import UserNotifications final class GeneralPreferencesViewController: NSViewController { + private var userNotificationSettings: UNNotificationSettings? + @IBOutlet var defaultRSSReaderPopup: NSPopUpButton! @IBOutlet var defaultBrowserPopup: NSPopUpButton! + @IBOutlet weak var showUnreadCountCheckbox: NSButton! private var rssReaderInfo = RSSReaderInfo() public override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { @@ -29,6 +33,7 @@ final class GeneralPreferencesViewController: NSViewController { override func viewWillAppear() { super.viewWillAppear() updateUI() + updateNotificationSettings() } // MARK: - Notifications @@ -58,6 +63,47 @@ final class GeneralPreferencesViewController: NSViewController { AppDefaults.shared.defaultBrowserID = bundleID updateUI() } + + + @IBAction func toggleShowingUnreadCount(_ sender: Any) { + guard let checkbox = sender as? NSButton else { return } + + guard userNotificationSettings != nil else { + DispatchQueue.main.async { + self.showUnreadCountCheckbox.setNextState() + } + return + } + + UNUserNotificationCenter.current().getNotificationSettings { (settings) in + self.updateNotificationSettings() + + if settings.authorizationStatus == .denied { + DispatchQueue.main.async { + self.showUnreadCountCheckbox.setNextState() + self.showNotificationsDeniedError() + } + } else if settings.authorizationStatus == .authorized { + DispatchQueue.main.async { + AppDefaults.shared.hideDockUnreadCount = (checkbox.state.rawValue == 0) + } + } else { + UNUserNotificationCenter.current().requestAuthorization(options: [.badge]) { (granted, error) in + self.updateNotificationSettings() + if granted { + DispatchQueue.main.async { + AppDefaults.shared.hideDockUnreadCount = checkbox.state.rawValue == 0 + NSApplication.shared.registerForRemoteNotifications() + } + } else { + DispatchQueue.main.async { + self.showUnreadCountCheckbox.setNextState() + } + } + } + } + } + } } // MARK: - Private @@ -70,8 +116,9 @@ private extension GeneralPreferencesViewController { func updateUI() { rssReaderInfo = RSSReaderInfo() - updateRSSReaderPopup() updateBrowserPopup() + updateRSSReaderPopup() + updateHideUnreadCountCheckbox() } func updateRSSReaderPopup() { @@ -166,8 +213,36 @@ private extension GeneralPreferencesViewController { defaultBrowserPopup.selectItem(at: defaultBrowserPopup.indexOfItem(withRepresentedObject: AppDefaults.shared.defaultBrowserID)) } -} + func updateHideUnreadCountCheckbox() { + showUnreadCountCheckbox.state = AppDefaults.shared.hideDockUnreadCount ? .off : .on + } + + func updateNotificationSettings() { + UNUserNotificationCenter.current().getNotificationSettings { (settings) in + self.userNotificationSettings = settings + if settings.authorizationStatus == .authorized { + DispatchQueue.main.async { + NSApplication.shared.registerForRemoteNotifications() + } + } + } + } + + func showNotificationsDeniedError() { + let updateAlert = NSAlert() + updateAlert.alertStyle = .informational + updateAlert.messageText = NSLocalizedString("Enable Notifications", comment: "Notifications") + updateAlert.informativeText = NSLocalizedString("To enable notifications, open Notifications in System Preferences, then find NetNewsWire in the list.", comment: "To enable notifications, open Notifications in System Preferences, then find NetNewsWire in the list.") + updateAlert.addButton(withTitle: NSLocalizedString("Open System Preferences", comment: "Open System Preferences")) + updateAlert.addButton(withTitle: NSLocalizedString("Close", comment: "Close")) + let modalResponse = updateAlert.runModal() + if modalResponse == .alertFirstButtonReturn { + NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.notifications")!) + } + } + +} // MARK: - RSSReaderInfo diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/ArticleExtractor.pdf b/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/ArticleExtractor.pdf deleted file mode 100644 index 4806aaecb..000000000 Binary files a/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/ArticleExtractor.pdf and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/ArticleExtractor.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/ArticleExtractor.png new file mode 100644 index 000000000..9af1d10d4 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/ArticleExtractor.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/ArticleExtractor@2x.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/ArticleExtractor@2x.png new file mode 100644 index 000000000..fa4f44b82 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/ArticleExtractor@2x.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/Contents.json b/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/Contents.json index 25d7d4f8e..68629067c 100644 --- a/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/Contents.json +++ b/Mac/Resources/Assets.xcassets/legacyArticleExtractor.imageset/Contents.json @@ -1,12 +1,22 @@ { "images" : [ { + "filename" : "ArticleExtractor.png", "idiom" : "universal", - "filename" : "ArticleExtractor.pdf" + "scale" : "1x" + }, + { + "filename" : "ArticleExtractor@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf deleted file mode 100644 index 28edebd24..000000000 Binary files a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.pdf and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.png new file mode 100644 index 000000000..bcde3ef96 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark@2x.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark@2x.png new file mode 100644 index 000000000..2ae7def3f Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/ArticleExtractorInactiveDark@2x.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/Contents.json b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/Contents.json index 3aa79833f..1db0cf9f9 100644 --- a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/Contents.json +++ b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveDark.imageset/Contents.json @@ -1,12 +1,22 @@ { "images" : [ { + "filename" : "ArticleExtractorInactiveDark.png", "idiom" : "universal", - "filename" : "ArticleExtractorInactiveDark.pdf" + "scale" : "1x" + }, + { + "filename" : "ArticleExtractorInactiveDark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight.pdf b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight.pdf deleted file mode 100644 index d09538c0c..000000000 Binary files a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight.pdf and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight.png new file mode 100644 index 000000000..20c37ccb7 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight@2x.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight@2x.png new file mode 100644 index 000000000..a3f13ebc2 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/ArticleExtractorInactiveLight@2x.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/Contents.json b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/Contents.json index 1dff9cf00..c704ed974 100644 --- a/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/Contents.json +++ b/Mac/Resources/Assets.xcassets/legacyArticleExtractorInactiveLight.imageset/Contents.json @@ -1,12 +1,22 @@ { "images" : [ { + "filename" : "ArticleExtractorInactiveLight.png", "idiom" : "universal", - "filename" : "ArticleExtractorInactiveLight.pdf" + "scale" : "1x" + }, + { + "filename" : "ArticleExtractorInactiveLight@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/ArticleExtractorProgress1.pdf b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/ArticleExtractorProgress1.pdf deleted file mode 100644 index b1737c594..000000000 Binary files a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/ArticleExtractorProgress1.pdf and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/ArticleExtractorProgress1.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/ArticleExtractorProgress1.png new file mode 100644 index 000000000..570378137 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/ArticleExtractorProgress1.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/ArticleExtractorProgress1@2x.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/ArticleExtractorProgress1@2x.png new file mode 100644 index 000000000..ad4d58f5b Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/ArticleExtractorProgress1@2x.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/Contents.json b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/Contents.json index 389bc2d91..4cd3ef91c 100644 --- a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/Contents.json +++ b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress1.imageset/Contents.json @@ -1,12 +1,22 @@ { "images" : [ { + "filename" : "ArticleExtractorProgress1.png", "idiom" : "universal", - "filename" : "ArticleExtractorProgress1.pdf" + "scale" : "1x" + }, + { + "filename" : "ArticleExtractorProgress1@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/ArticleExtractorProgress2.pdf b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/ArticleExtractorProgress2.pdf deleted file mode 100644 index 939dcc5cc..000000000 Binary files a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/ArticleExtractorProgress2.pdf and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/ArticleExtractorProgress2.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/ArticleExtractorProgress2.png new file mode 100644 index 000000000..2d9b473a3 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/ArticleExtractorProgress2.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/ArticleExtractorProgress2@2x.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/ArticleExtractorProgress2@2x.png new file mode 100644 index 000000000..f7883efec Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/ArticleExtractorProgress2@2x.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/Contents.json b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/Contents.json index c3b85cb64..21de8a7fd 100644 --- a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/Contents.json +++ b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress2.imageset/Contents.json @@ -1,12 +1,22 @@ { "images" : [ { + "filename" : "ArticleExtractorProgress2.png", "idiom" : "universal", - "filename" : "ArticleExtractorProgress2.pdf" + "scale" : "1x" + }, + { + "filename" : "ArticleExtractorProgress2@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/ArticleExtractorProgress3.pdf b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/ArticleExtractorProgress3.pdf deleted file mode 100644 index d27a6269b..000000000 Binary files a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/ArticleExtractorProgress3.pdf and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/ArticleExtractorProgress3.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/ArticleExtractorProgress3.png new file mode 100644 index 000000000..ee2ef5db6 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/ArticleExtractorProgress3.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/ArticleExtractorProgress3@2x.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/ArticleExtractorProgress3@2x.png new file mode 100644 index 000000000..0a809afc7 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/ArticleExtractorProgress3@2x.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/Contents.json b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/Contents.json index a9ce26ffe..597630fa6 100644 --- a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/Contents.json +++ b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress3.imageset/Contents.json @@ -1,12 +1,22 @@ { "images" : [ { + "filename" : "ArticleExtractorProgress3.png", "idiom" : "universal", - "filename" : "ArticleExtractorProgress3.pdf" + "scale" : "1x" + }, + { + "filename" : "ArticleExtractorProgress3@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/ArticleExtractorProgress4.pdf b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/ArticleExtractorProgress4.pdf deleted file mode 100644 index 2e27e98c6..000000000 Binary files a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/ArticleExtractorProgress4.pdf and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/ArticleExtractorProgress4.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/ArticleExtractorProgress4.png new file mode 100644 index 000000000..54cb59f04 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/ArticleExtractorProgress4.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/ArticleExtractorProgress4@2x.png b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/ArticleExtractorProgress4@2x.png new file mode 100644 index 000000000..2c9bad220 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/ArticleExtractorProgress4@2x.png differ diff --git a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/Contents.json b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/Contents.json index cb931de4b..7f2137461 100644 --- a/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/Contents.json +++ b/Mac/Resources/Assets.xcassets/legacyArticleExtractorProgress4.imageset/Contents.json @@ -1,12 +1,22 @@ { "images" : [ { + "filename" : "ArticleExtractorProgress4.png", "idiom" : "universal", - "filename" : "ArticleExtractorProgress4.pdf" + "scale" : "1x" + }, + { + "filename" : "ArticleExtractorProgress4@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Mac/Resources/Credits.rtf b/Mac/Resources/Credits.rtf index d3d86734d..83b8c5565 100644 --- a/Mac/Resources/Credits.rtf +++ b/Mac/Resources/Credits.rtf @@ -1,4 +1,4 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2511 +{\rtf1\ansi\ansicpg1252\cocoartf2513 \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande-Bold;\f1\fnil\fcharset0 LucidaGrande;} {\colortbl;\red255\green255\blue255;\red0\green0\blue0;} {\*\expandedcolortbl;;\cssrgb\c0\c0\c0\cname textColor;} @@ -18,10 +18,13 @@ By Brent Simmons\ \f0\b \cf2 Credits: \f1\b0 \ \pard\pardeftab720\li360\sa60\partightenfactor0 -\cf2 App icon and most other icons: {\field{\*\fldinst{HYPERLINK "https://twitter.com/BradEllis"}}{\fldrslt Brad Ellis}}\ -Major code contributors: {\field{\*\fldinst{HYPERLINK "https://github.com/olofhellman"}}{\fldrslt Olof Hellman}}, {\field{\*\fldinst{HYPERLINK "https://github.com/vincode-io"}}{\fldrslt Maurice Parker}}, and {\field{\*\fldinst{HYPERLINK "https://github.com/danielpunkass"}}{\fldrslt Daniel Jalkut\ -}}Help book by {\field{\*\fldinst{HYPERLINK "https://nostodnayr.net/"}}{\fldrslt Ryan Dotson}}\ -Difficult infrastructure by {\field{\*\fldinst{HYPERLINK "https://rhonabwy.com/"}}{\fldrslt Joe Heck}}\ +\cf2 Lead developer: {\field{\*\fldinst{HYPERLINK "https://github.com/vincode-io"}}{\fldrslt Maurice Parker}}\ +App icon and most other icons: {\field{\*\fldinst{HYPERLINK "https://twitter.com/BradEllis"}}{\fldrslt Brad Ellis}}\ +Feedly syncing: {\field{\*\fldinst{HYPERLINK "https://twitter.com/kielgillard"}}{\fldrslt Kiel Gillard}}\ +Under-the-hood magic and CSS stylin\'92s: {\field{\*\fldinst{HYPERLINK "https://github.com/wevah"}}{\fldrslt Nate Weaver}}\ +Newsfoot (JS footnote displayer): {\field{\*\fldinst{HYPERLINK "https://github.com/brehaut/"}}{\fldrslt Andrew Brehaut}}\ +Help book: {\field{\*\fldinst{HYPERLINK "https://nostodnayr.net/"}}{\fldrslt Ryan Dotson}}\ +And featuring contributions from {\field{\*\fldinst{HYPERLINK "https://github.com/danielpunkass"}}{\fldrslt Daniel Jalkut}}, {\field{\*\fldinst{HYPERLINK "https://rhonabwy.com/"}}{\fldrslt Joe Heck}}, {\field{\*\fldinst{HYPERLINK "https://github.com/olofhellman"}}{\fldrslt Olof Hellman}}, {\field{\*\fldinst{HYPERLINK "https://blog.rizwan.dev/"}}{\fldrslt Rizwan Mohamed Ibrahim}}, {\field{\*\fldinst{HYPERLINK "https://stuartbreckenridge.com/"}}{\fldrslt Stuart Breckenridge}}, {\field{\*\fldinst{HYPERLINK "https://twitter.com/philviso"}}{\fldrslt Phil Viso}}, and {\field{\*\fldinst{HYPERLINK "https://github.com/Ranchero-Software/NetNewsWire/graphs/contributors"}}{\fldrslt many more}}!\ \ \pard\pardeftab720\sa60\partightenfactor0 @@ -44,5 +47,5 @@ Difficult infrastructure by {\field{\*\fldinst{HYPERLINK "https://rhonabwy.com/" \f0\b \cf2 Dedication:\ \pard\pardeftab720\li360\sa60\partightenfactor0 -\f1\b0 \cf2 NetNewsWire 5.0 is dedicated to Aaron Swartz, Derek Miller, and Alex King, who helped with earlier versions of NetNewsWire, who I miss.\ +\f1\b0 \cf2 NetNewsWire 5.1 is dedicated to everyone working to save democracy around the world.\ } \ No newline at end of file diff --git a/Mac/Resources/Info.plist b/Mac/Resources/Info.plist index 9ea6c6e17..f0a6ae9eb 100644 --- a/Mac/Resources/Info.plist +++ b/Mac/Resources/Info.plist @@ -52,7 +52,7 @@ NSAppleScriptEnabled NSHumanReadableCopyright - Copyright © 2002-2019 Brent Simmons. All rights reserved. + Copyright © 2002-2020 Brent Simmons. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass @@ -60,9 +60,9 @@ OSAScriptingDefinition NetNewsWire.sdef SUFeedURL - https://ranchero.com/downloads/netnewswire-5.1-beta.xml + https://ranchero.com/downloads/netnewswire-release.xml FeedURLForTestBuilds - https://ranchero.com/downloads/netnewswire-5.1-beta.xml + https://ranchero.com/downloads/netnewswire-beta.xml UserAgent NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/) OrganizationIdentifier diff --git a/Mac/Resources/NetNewsWire-dev.entitlements b/Mac/Resources/NetNewsWire-dev.entitlements index a252d23a8..f5040f6eb 100644 --- a/Mac/Resources/NetNewsWire-dev.entitlements +++ b/Mac/Resources/NetNewsWire-dev.entitlements @@ -3,7 +3,7 @@ com.apple.security.app-sandbox - + com.apple.security.automation.apple-events com.apple.security.files.user-selected.read-write diff --git a/Mac/Resources/NetNewsWire.entitlements b/Mac/Resources/NetNewsWire.entitlements index 4e4af4cdb..186dc9437 100644 --- a/Mac/Resources/NetNewsWire.entitlements +++ b/Mac/Resources/NetNewsWire.entitlements @@ -12,18 +12,8 @@ CloudKit - com.apple.security.app-sandbox - - com.apple.security.application-groups - - group.$(ORGANIZATION_IDENTIFIER).NetNewsWire - com.apple.security.automation.apple-events - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - com.apple.security.temporary-exception.apple-events com.red-sweater.marsedit4 diff --git a/Mac/SafariExtension/Info.plist b/Mac/SafariExtension/Info.plist index 5f4fa8c03..e38797986 100644 --- a/Mac/SafariExtension/Info.plist +++ b/Mac/SafariExtension/Info.plist @@ -57,7 +57,7 @@ NSHumanReadableCopyright - Copyright © 2019 Ranchero Software. All rights reserved. + Copyright © 2019-2020 Brent Simmons. All rights reserved. NSHumanReadableDescription This extension adds a Safari toolbar button for easily subscribing to the syndication feed for the current page. diff --git a/Multiplatform/macOS/Info.plist b/Multiplatform/macOS/Info.plist index ec33c4818..9aad2f0d9 100644 --- a/Multiplatform/macOS/Info.plist +++ b/Multiplatform/macOS/Info.plist @@ -50,8 +50,8 @@ SUFeedURL - https://ranchero.com/downloads/netnewswire-5.1-beta.xml + https://ranchero.com/downloads/netnewswire-release.xml FeedURLForTestBuilds - https://ranchero.com/downloads/netnewswire-5.1-beta.xml + https://ranchero.com/downloads/netnewswire-beta.xml diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift index 6354600ba..21074d617 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift @@ -277,8 +277,14 @@ extension AddAccountModel { // MARK:- OAuthAccountAuthorizationOperationDelegate extension AddAccountModel: OAuthAccountAuthorizationOperationDelegate { func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { + accountIsAuthenticating = false accountAdded = true + + // macOS only: `ASWebAuthenticationSession` leaves the browser in the foreground. + // Ensure the app is in the foreground so the user can see their Feedly account load. + NSApplication.shared.activate(ignoringOtherApps: true) + account.refreshAll { [weak self] result in switch result { case .success: @@ -291,6 +297,11 @@ extension AddAccountModel: OAuthAccountAuthorizationOperationDelegate { func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { accountIsAuthenticating = false + + // macOS only: `ASWebAuthenticationSession` leaves the browser in the foreground. + // Ensure the app is in the foreground so the user can see the error. + NSApplication.shared.activate(ignoringOtherApps: true) + addAccountError = .other(error: error) } } diff --git a/Multiplatform/macOS/macOS-dev.entitlements b/Multiplatform/macOS/macOS-dev.entitlements index f6af14e2f..4b0c04750 100644 --- a/Multiplatform/macOS/macOS-dev.entitlements +++ b/Multiplatform/macOS/macOS-dev.entitlements @@ -3,7 +3,7 @@ com.apple.security.app-sandbox - + com.apple.security.automation.apple-events com.apple.security.files.user-selected.read-write diff --git a/Multiplatform/macOS/macOS.entitlements b/Multiplatform/macOS/macOS.entitlements index 7b1d03115..cfbbe8b53 100644 --- a/Multiplatform/macOS/macOS.entitlements +++ b/Multiplatform/macOS/macOS.entitlements @@ -13,7 +13,7 @@ CloudKit com.apple.security.app-sandbox - + com.apple.security.automation.apple-events com.apple.security.files.user-selected.read-write diff --git a/Shared/Article Rendering/shared.css b/Shared/Article Rendering/shared.css index 9b4898e3c..af7fc3ced 100644 --- a/Shared/Article Rendering/shared.css +++ b/Shared/Article Rendering/shared.css @@ -32,18 +32,18 @@ a:hover { --header-color: rgba(0, 0, 0, 0.3); --body-code-color: #666; --system-message-color: #cbcbcb; - --feedlink-color: rgba(0, 0, 0, 0.3); + --feedlink-color: rgba(255, 0, 0, 0.6); --article-title-color: #333; - --article-date-color: rgba(0, 0, 0, 0.5); + --article-date-color: rgba(0, 0, 0, 0.3); --table-cell-border-color: lightgray; } @media(prefers-color-scheme: dark) { :root { - --header-color: #d2d2d2; + --header-color: rgba(94, 158, 244, 1); --body-code-color: #b2b2b2; --system-message-color: #5f5f5f; - --feedlink-color: rgba(255, 255, 255, 0.7); + --feedlink-color: rgba(94, 158, 244, 1); --article-title-color: #e0e0e0; --article-date-color: rgba(255, 255, 255, 0.5); --table-cell-border-color: dimgray; @@ -57,10 +57,14 @@ body { body .headerTable { border-bottom: 1px solid var(--header-table-border-color); + color: var(--header-color); } body .header { color: var(--header-color); } +body .header a:link, .header a:visited { + color: var(--header-color); +} body code, body pre { color: var(--body-code-color); diff --git a/Shared/Timeline/FetchRequestOperation.swift b/Shared/Timeline/FetchRequestOperation.swift index ad3d0b729..74ba5d098 100644 --- a/Shared/Timeline/FetchRequestOperation.swift +++ b/Shared/Timeline/FetchRequestOperation.swift @@ -19,17 +19,17 @@ typealias FetchRequestOperationResultBlock = (Set
, FetchRequestOperatio final class FetchRequestOperation { let id: Int - let readFilter: Bool + let readFilterEnabledTable: [FeedIdentifier: Bool] let resultBlock: FetchRequestOperationResultBlock var isCanceled = false var isFinished = false - private let representedObjects: [Any] + private let fetchers: [ArticleFetcher] - init(id: Int, readFilter: Bool, representedObjects: [Any], resultBlock: @escaping FetchRequestOperationResultBlock) { + init(id: Int, readFilterEnabledTable: [FeedIdentifier: Bool], fetchers: [ArticleFetcher], resultBlock: @escaping FetchRequestOperationResultBlock) { precondition(Thread.isMainThread) self.id = id - self.readFilter = readFilter - self.representedObjects = representedObjects + self.readFilterEnabledTable = readFilterEnabledTable + self.fetchers = fetchers self.resultBlock = resultBlock } @@ -51,15 +51,14 @@ final class FetchRequestOperation { return } - let articleFetchers = representedObjects.compactMap{ $0 as? ArticleFetcher } - if articleFetchers.isEmpty { + if fetchers.isEmpty { isFinished = true resultBlock(Set
(), self) callCompletionIfNeeded() return } - let numberOfFetchers = articleFetchers.count + let numberOfFetchers = fetchers.count var fetchersReturned = 0 var fetchedArticles = Set
() @@ -81,19 +80,19 @@ final class FetchRequestOperation { } } - for articleFetcher in articleFetchers { - if readFilter { - articleFetcher.fetchUnreadArticlesAsync { articleSetResult in - let articles = (try? articleSetResult.get()) ?? Set
() - process(articles) - } - } - else { - articleFetcher.fetchArticlesAsync { articleSetResult in + for fetcher in fetchers { + if (fetcher as? Feed)?.readFiltered(readFilterEnabledTable: readFilterEnabledTable) ?? true { + fetcher.fetchUnreadArticlesAsync { articleSetResult in + let articles = (try? articleSetResult.get()) ?? Set
() + process(articles) + } + } else { + fetcher.fetchArticlesAsync { articleSetResult in let articles = (try? articleSetResult.get()) ?? Set
() process(articles) } } + } } } diff --git a/iOS/Add/AddFeedViewController.swift b/iOS/Add/AddFeedViewController.swift index ed5f9cf9c..c857d2bb8 100644 --- a/iOS/Add/AddFeedViewController.swift +++ b/iOS/Add/AddFeedViewController.swift @@ -100,6 +100,7 @@ class AddFeedViewController: UITableViewController { let normalizedURLString = urlString.normalizedURL guard !normalizedURLString.isEmpty, let url = URL(unicodeString: normalizedURLString) else { + delegate?.processingDidCancel() return } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index c0f692411..cfef1131e 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -1928,7 +1928,8 @@ private extension SceneCoordinator { precondition(Thread.isMainThread) cancelPendingAsyncFetches() - let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: isReadArticlesFiltered, representedObjects: representedObjects) { [weak self] (articles, operation) in + let fetchers = representedObjects.compactMap { $0 as? ArticleFetcher } + let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilterEnabledTable: readFilterEnabledTable, fetchers: fetchers) { [weak self] (articles, operation) in precondition(Thread.isMainThread) guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else { return diff --git a/xcconfig/common/NetNewsWire_mac_target_common.xcconfig b/xcconfig/common/NetNewsWire_mac_target_common.xcconfig index 77b811715..aaaab96b3 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.1d3 -CURRENT_PROJECT_VERSION = 3003 +MARKETING_VERSION = 5.1.1 +CURRENT_PROJECT_VERSION = 3012 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon