From 0ac3ba0bbb465ee3fa8b6a8f9e9a0cdeaf943d3f Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 13 Apr 2021 07:38:41 -0500 Subject: [PATCH 1/5] Change force unwraps so that we just fail on the move instead of crashing --- iOS/MasterFeed/MasterFeedViewController.swift | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index ee332f0a0..c933c3b43 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -370,22 +370,28 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { return IndexPath(row: 0, section: destIndexPath.section) } else { let identifier = makeIdentifier(sortedNodes[index]) - let candidateIndexPath = dataSource.indexPath(for: identifier)! - let movementAdjustment = sourceIndexPath < destIndexPath ? 1 : 0 - return IndexPath(row: candidateIndexPath.row - movementAdjustment, section: candidateIndexPath.section) + if let candidateIndexPath = dataSource.indexPath(for: identifier) { + let movementAdjustment = sourceIndexPath < destIndexPath ? 1 : 0 + return IndexPath(row: candidateIndexPath.row - movementAdjustment, section: candidateIndexPath.section) + } else { + return sourceIndexPath + } } } else { if index >= sortedNodes.count { let identifier = makeIdentifier(sortedNodes[sortedNodes.count - 1]) - let lastSortedIndexPath = dataSource.indexPath(for: identifier)! - let movementAdjustment = sourceIndexPath > destIndexPath ? 1 : 0 - return IndexPath(row: lastSortedIndexPath.row + movementAdjustment, section: lastSortedIndexPath.section) + if let lastSortedIndexPath = dataSource.indexPath(for: identifier) { + let movementAdjustment = sourceIndexPath > destIndexPath ? 1 : 0 + return IndexPath(row: lastSortedIndexPath.row + movementAdjustment, section: lastSortedIndexPath.section) + } else { + return sourceIndexPath + } } else { let movementAdjustment = sourceIndexPath < destIndexPath ? 1 : 0 let identifer = makeIdentifier(sortedNodes[index - movementAdjustment]) - return dataSource.indexPath(for: identifer)! + return dataSource.indexPath(for: identifer) ?? sourceIndexPath } } From 1b3a070389cd8b0941cc8ba612c95bbd121837d1 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 13 Apr 2021 12:00:50 +0800 Subject: [PATCH 2/5] Fixes #2994 Removes force unwrapping and returns an empty string if the pubDate cannot be determined. --- Widget/Shared Views/ArticleItemView.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Widget/Shared Views/ArticleItemView.swift b/Widget/Shared Views/ArticleItemView.swift index a35f07826..455cbaaf9 100644 --- a/Widget/Shared Views/ArticleItemView.swift +++ b/Widget/Shared Views/ArticleItemView.swift @@ -65,6 +65,10 @@ struct ArticleItemView: View { displayFormatter.dateStyle = .medium displayFormatter.timeStyle = .none - return displayFormatter.string(from: date!) + guard let dateFromDateFormatter = date else { + return "" + } + + return displayFormatter.string(from: dateFromDateFormatter) } } From 70cf3a303a3f034aceae32ce770215ee193b1424 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 13 Apr 2021 12:17:31 +0800 Subject: [PATCH 3/5] reworked gaurd statement --- Widget/Shared Views/ArticleItemView.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Widget/Shared Views/ArticleItemView.swift b/Widget/Shared Views/ArticleItemView.swift index 455cbaaf9..c3ed7467d 100644 --- a/Widget/Shared Views/ArticleItemView.swift +++ b/Widget/Shared Views/ArticleItemView.swift @@ -59,16 +59,14 @@ struct ArticleItemView: View { func pubDate(_ dateString: String) -> String { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z" - let date = dateFormatter.date(from: dateString) + guard let date = dateFormatter.date(from: dateString) else { + return "" + } let displayFormatter = DateFormatter() displayFormatter.dateStyle = .medium displayFormatter.timeStyle = .none - guard let dateFromDateFormatter = date else { - return "" - } - - return displayFormatter.string(from: dateFromDateFormatter) + return displayFormatter.string(from: date) } } From ebc10b54c1fadc08062fe5ef3a4c591f38ef19b2 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Tue, 13 Apr 2021 19:52:48 -0700 Subject: [PATCH 4/5] Bump build to 601. --- xcconfig/common/NetNewsWire_ios_target_common.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index 28898b46b..e8b2c3545 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it MARKETING_VERSION = 6.0 -CURRENT_PROJECT_VERSION = 600 +CURRENT_PROJECT_VERSION = 601 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon From 091ef7aba2fd3cb69374aa7e73a947501ec29f15 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 15 Apr 2021 14:13:15 -0500 Subject: [PATCH 5/5] Exclude Inoreader from article status syncs --- Account/Sources/Account/Account.swift | 16 +------------ Account/Sources/Account/AccountDelegate.swift | 1 + .../CloudKit/CloudKitAccountDelegate.swift | 18 +++++++++++++++ .../FeedWranglerAccountDelegate.swift | 18 +++++++++++++++ .../Feedbin/FeedbinAccountDelegate.swift | 18 +++++++++++++++ .../Feedly/FeedlyAccountDelegate.swift | 18 +++++++++++++++ .../LocalAccount/LocalAccountDelegate.swift | 4 ++++ .../NewsBlur/NewsBlurAccountDelegate.swift | 18 +++++++++++++++ .../ReaderAPI/ReaderAPIAccountDelegate.swift | 23 +++++++++++++++++++ 9 files changed, 119 insertions(+), 15 deletions(-) diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index d951d7ae4..7f9b3ccb3 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -443,21 +443,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } public func syncArticleStatus(completion: ((Result) -> Void)? = nil) { - delegate.sendArticleStatus(for: self) { [unowned self] result in - switch result { - case .success: - self.delegate.refreshArticleStatus(for: self) { result in - switch result { - case .success: - completion?(.success(())) - case .failure(let error): - completion?(.failure(error)) - } - } - case .failure(let error): - completion?(.failure(error)) - } - } + delegate.syncArticleStatus(for: self, completion: completion) } public func importOPML(_ opmlFile: URL, completion: @escaping (Result) -> Void) { diff --git a/Account/Sources/Account/AccountDelegate.swift b/Account/Sources/Account/AccountDelegate.swift index 20faf1a09..69f5091d2 100644 --- a/Account/Sources/Account/AccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegate.swift @@ -26,6 +26,7 @@ protocol AccountDelegate { func receiveRemoteNotification(for account: Account, userInfo: [AnyHashable : Any], completion: @escaping () -> Void) func refreshAll(for account: Account, completion: @escaping (Result) -> Void) + func syncArticleStatus(for account: Account, completion: ((Result) -> Void)?) func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index 00d263530..8d777badf 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -92,6 +92,24 @@ final class CloudKitAccountDelegate: AccountDelegate { standardRefreshAll(for: account, completion: completion) } + func syncArticleStatus(for account: Account, completion: ((Result) -> Void)? = nil) { + sendArticleStatus(for: account) { result in + switch result { + case .success: + self.refreshArticleStatus(for: account) { result in + switch result { + case .success: + completion?(.success(())) + case .failure(let error): + completion?(.failure(error)) + } + } + case .failure(let error): + completion?(.failure(error)) + } + } + } + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { sendArticleStatus(for: account, showProgress: false, completion: completion) } diff --git a/Account/Sources/Account/FeedWrangler/FeedWranglerAccountDelegate.swift b/Account/Sources/Account/FeedWrangler/FeedWranglerAccountDelegate.swift index 427fc34dc..d7b6dfe2a 100644 --- a/Account/Sources/Account/FeedWrangler/FeedWranglerAccountDelegate.swift +++ b/Account/Sources/Account/FeedWrangler/FeedWranglerAccountDelegate.swift @@ -150,6 +150,24 @@ final class FeedWranglerAccountDelegate: AccountDelegate { } } + func syncArticleStatus(for account: Account, completion: ((Result) -> Void)? = nil) { + sendArticleStatus(for: account) { result in + switch result { + case .success: + self.refreshArticleStatus(for: account) { result in + switch result { + case .success: + completion?(.success(())) + case .failure(let error): + completion?(.failure(error)) + } + } + case .failure(let error): + completion?(.failure(error)) + } + } + } + func refreshArticles(for account: Account, page: Int = 0, completion: @escaping ((Result) -> Void)) { os_log(.debug, log: log, "Refreshing articles, page: %d...", page) diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index d1ba5ee20..cbb44d837 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -112,6 +112,24 @@ final class FeedbinAccountDelegate: AccountDelegate { } + func syncArticleStatus(for account: Account, completion: ((Result) -> Void)? = nil) { + sendArticleStatus(for: account) { result in + switch result { + case .success: + self.refreshArticleStatus(for: account) { result in + switch result { + case .success: + completion?(.success(())) + case .failure(let error): + completion?(.failure(error)) + } + } + case .failure(let error): + completion?(.failure(error)) + } + } + } + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { os_log(.debug, log: log, "Sending article statuses...") diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift index 78526abc9..0f9de2e5b 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift @@ -145,6 +145,24 @@ final class FeedlyAccountDelegate: AccountDelegate { operationQueue.add(syncAllOperation) } + func syncArticleStatus(for account: Account, completion: ((Result) -> Void)? = nil) { + sendArticleStatus(for: account) { result in + switch result { + case .success: + self.refreshArticleStatus(for: account) { result in + switch result { + case .success: + completion?(.success(())) + case .failure(let error): + completion?(.failure(error)) + } + } + case .failure(let error): + completion?(.failure(error)) + } + } + } + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { // Ensure remote articles have the same status as they do locally. let send = FeedlySendArticleStatusesOperation(database: database, service: caller, log: log) diff --git a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index 4c717a2ac..db34a52e6 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -96,6 +96,10 @@ final class LocalAccountDelegate: AccountDelegate { } + func syncArticleStatus(for account: Account, completion: ((Result) -> Void)? = nil) { + completion?(.success(())) + } + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { completion(.success(())) } diff --git a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift index 735ee1a35..49c5a5af0 100644 --- a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift +++ b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift @@ -114,6 +114,24 @@ final class NewsBlurAccountDelegate: AccountDelegate { } } + func syncArticleStatus(for account: Account, completion: ((Result) -> Void)? = nil) { + sendArticleStatus(for: account) { result in + switch result { + case .success: + self.refreshArticleStatus(for: account) { result in + switch result { + case .success: + completion?(.success(())) + case .failure(let error): + completion?(.failure(error)) + } + } + case .failure(let error): + completion?(.failure(error)) + } + } + } + func sendArticleStatus(for account: Account, completion: @escaping (Result) -> ()) { os_log(.debug, log: log, "Sending story statuses...") diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index c143a426f..6745403b1 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -131,6 +131,29 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } + func syncArticleStatus(for account: Account, completion: ((Result) -> Void)? = nil) { + guard variant != .inoreader else { + completion?(.success(())) + return + } + + sendArticleStatus(for: account) { result in + switch result { + case .success: + self.refreshArticleStatus(for: account) { result in + switch result { + case .success: + completion?(.success(())) + case .failure(let error): + completion?(.failure(error)) + } + } + case .failure(let error): + completion?(.failure(error)) + } + } + } + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { os_log(.debug, log: log, "Sending article statuses...")