From cca3089218305429f5a693306b8ef916b23afa2a Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 27 Sep 2019 21:56:16 -0700
Subject: [PATCH 001/189] Update RSParser.
---
submodules/RSParser | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/submodules/RSParser b/submodules/RSParser
index d3fe846f8..f01129d76 160000
--- a/submodules/RSParser
+++ b/submodules/RSParser
@@ -1 +1 @@
-Subproject commit d3fe846f8969f8914d233242b711f4314ec1cb17
+Subproject commit f01129d762eba20cd11a680bbde651ca75639ef3
From 07a631309cddff6adf71a9a3dffdb46f5e63bf6d Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 27 Sep 2019 22:59:15 -0700
Subject: [PATCH 002/189] Update RSWeb.
---
submodules/RSWeb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/submodules/RSWeb b/submodules/RSWeb
index d140c97a1..9cb7ca961 160000
--- a/submodules/RSWeb
+++ b/submodules/RSWeb
@@ -1 +1 @@
-Subproject commit d140c97a13799a38fe62c09d1719c3963c3df3ce
+Subproject commit 9cb7ca96182b3320882522708a2b4dcdaafb07f6
From d7b45a14137725c9b55dfa5c66e979c3b976b931 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 27 Sep 2019 23:01:31 -0700
Subject: [PATCH 003/189] =?UTF-8?q?Change=20parseDatePublished()=20to=20a?=
=?UTF-8?q?=20lazy=20var=20parsedDatePublished=20=E2=80=94=20it=20appeared?=
=?UTF-8?q?=20that=20it=20was=20getting=20called=20more=20than=20once,=20a?=
=?UTF-8?q?nd=20date=20parsing=20is=20expensive.=20Also:=20use=20RSDateWit?=
=?UTF-8?q?hString=20rather=20than=20an=20NSDateFormatter,=20since=20NSDat?=
=?UTF-8?q?eFormatter=20is=20so=20massively=20slow.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Feedbin/FeedbinAccountDelegate.swift | 2 +-
Frameworks/Account/Feedbin/FeedbinEntry.swift | 28 +++++++++----------
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift
index f7b801b8c..bbd185238 100644
--- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift
+++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift
@@ -1109,7 +1109,7 @@ private extension FeedbinAccountDelegate {
let parsedItems: [ParsedItem] = entries.map { entry in
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)
+ 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.parsedDatePublished, dateModified: nil, authors: authors, tags: nil, attachments: nil)
}
return Set(parsedItems)
diff --git a/Frameworks/Account/Feedbin/FeedbinEntry.swift b/Frameworks/Account/Feedbin/FeedbinEntry.swift
index 8d73a51da..82b40e186 100644
--- a/Frameworks/Account/Feedbin/FeedbinEntry.swift
+++ b/Frameworks/Account/Feedbin/FeedbinEntry.swift
@@ -10,7 +10,7 @@ import Foundation
import RSParser
import RSCore
-struct FeedbinEntry: Codable {
+final class FeedbinEntry: Codable {
let articleID: Int
let feedID: Int
@@ -23,6 +23,19 @@ struct FeedbinEntry: Codable {
let dateArrived: String?
let jsonFeed: FeedbinEntryJSONFeed?
+ // Feedbin dates can't be decoded by the JSONDecoding 8601 decoding strategy. Feedbin
+ // requires a very specific date formatter to work and even then it fails occasionally.
+ // Rather than loose all the entries we only lose the one date by decoding as a string
+ // and letting the one date fail when parsed.
+ lazy var parsedDatePublished: Date? = {
+ if let datePublished = datePublished {
+ return RSDateWithString(datePublished)
+ }
+ else {
+ return nil
+ }
+ }()
+
enum CodingKeys: String, CodingKey {
case articleID = "id"
case feedID = "feed_id"
@@ -35,19 +48,6 @@ struct FeedbinEntry: Codable {
case dateArrived = "created_at"
case jsonFeed = "json_feed"
}
-
- // Feedbin dates can't be decoded by the JSONDecoding 8601 decoding strategy. Feedbin
- // requires a very specific date formatter to work and even then it fails occasionally.
- // Rather than loose all the entries we only lose the one date by decoding as a string
- // and letting the one date fail when parsed.
- func parseDatePublished() -> Date? {
- if datePublished != nil {
- return FeedbinDate.formatter.date(from: datePublished!)
- } else {
- return nil
- }
- }
-
}
struct FeedbinEntryJSONFeed: Codable {
From 35240eb2cfe6218084d1627979ec935e192efe31 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Fri, 27 Sep 2019 23:02:14 -0700
Subject: [PATCH 004/189] Update RSParser and RSWeb.
---
submodules/RSParser | 2 +-
submodules/RSWeb | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/submodules/RSParser b/submodules/RSParser
index d3fe846f8..f01129d76 160000
--- a/submodules/RSParser
+++ b/submodules/RSParser
@@ -1 +1 @@
-Subproject commit d3fe846f8969f8914d233242b711f4314ec1cb17
+Subproject commit f01129d762eba20cd11a680bbde651ca75639ef3
diff --git a/submodules/RSWeb b/submodules/RSWeb
index d140c97a1..9cb7ca961 160000
--- a/submodules/RSWeb
+++ b/submodules/RSWeb
@@ -1 +1 @@
-Subproject commit d140c97a13799a38fe62c09d1719c3963c3df3ce
+Subproject commit 9cb7ca96182b3320882522708a2b4dcdaafb07f6
From 7c269214874c8214763345fd5a4f7ca975a0ab81 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Sat, 28 Sep 2019 06:20:06 -0500
Subject: [PATCH 005/189] Change Feed Inspector Reader View setting layout
---
Mac/Inspector/Inspector.storyboard | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/Mac/Inspector/Inspector.storyboard b/Mac/Inspector/Inspector.storyboard
index ed2237926..4f5f22bf5 100644
--- a/Mac/Inspector/Inspector.storyboard
+++ b/Mac/Inspector/Inspector.storyboard
@@ -1,8 +1,8 @@
-
+
-
+
@@ -63,7 +63,7 @@ Field
-
+
@@ -71,7 +71,7 @@ Field
-
+
@@ -79,7 +79,7 @@ Field
-
+
@@ -87,7 +87,7 @@ Field
-
+
@@ -95,8 +95,8 @@ Field
-
-
+
+
@@ -106,12 +106,12 @@ Field
+
-
-
+
@@ -122,8 +122,8 @@ Field
-
+
From f785e9f839f71cd325d2e20c9a1d356c670688f5 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Sat, 28 Sep 2019 06:35:21 -0500
Subject: [PATCH 006/189] Update the feed icon if it wasn't available when the
inspector was first shown
---
iOS/Inspector/FeedInspectorView.swift | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/iOS/Inspector/FeedInspectorView.swift b/iOS/Inspector/FeedInspectorView.swift
index 07b1c2a7a..66d15d2f9 100644
--- a/iOS/Inspector/FeedInspectorView.swift
+++ b/iOS/Inspector/FeedInspectorView.swift
@@ -30,7 +30,7 @@ struct FeedInspectorView : View {
}) {
TextField("Feed Name", text: $viewModel.name)
Toggle(isOn: $viewModel.isArticleExtractorAlwaysOn) {
- Text("Reader View is always on")
+ Text("Always Show Reader View")
}
}
Section(header: Text("HOME PAGE")) {
@@ -54,6 +54,7 @@ struct FeedInspectorView : View {
init(feed: Feed) {
self.feed = feed
+ NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
}
var image: UIImage {
@@ -97,7 +98,11 @@ struct FeedInspectorView : View {
var feedLinkURL: String {
return feed.url
}
-
+
+ @objc func feedIconDidBecomeAvailable(_ notification: Notification) {
+ objectWillChange.send()
+ }
+
}
}
From 09a3a03fc47757ea041d58ece6d576f600d1ad00 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Sat, 28 Sep 2019 07:00:18 -0500
Subject: [PATCH 007/189] Add Get Info context menu option
---
iOS/AppAssets.swift | 4 +++
iOS/MasterFeed/MasterFeedViewController.swift | 33 +++++++++++++++++++
iOS/SceneCoordinator.swift | 6 ++++
3 files changed, 43 insertions(+)
diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift
index 1ad368e43..a31ac6ea5 100644
--- a/iOS/AppAssets.swift
+++ b/iOS/AppAssets.swift
@@ -68,6 +68,10 @@ struct AppAssets {
return RSImage(named: "faviconTemplateImage")!
}()
+ static var infoImage: UIImage = {
+ UIImage(systemName: "info.circle")!
+ }()
+
static var markAllInFeedAsReadImage: UIImage = {
return UIImage(systemName: "asterisk.circle")!
}()
diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift
index a0681824a..f6e0c823b 100644
--- a/iOS/MasterFeed/MasterFeedViewController.swift
+++ b/iOS/MasterFeed/MasterFeedViewController.swift
@@ -229,6 +229,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
popoverController.sourceRect = CGRect(x: view.frame.size.width/2, y: view.frame.size.height/2, width: 1, height: 1)
}
+ if let action = self.getInfoAlertAction(indexPath: indexPath, completionHandler: completionHandler) {
+ alert.addAction(action)
+ }
+
if let action = self.homePageAlertAction(indexPath: indexPath, completionHandler: completionHandler) {
alert.addAction(action)
}
@@ -697,6 +701,10 @@ private extension MasterFeedViewController {
var actions = [UIAction]()
+ if let inspectorAction = self.getInfoAction(indexPath: indexPath) {
+ actions.append(inspectorAction)
+ }
+
if let homePageAction = self.homePageAction(indexPath: indexPath) {
actions.append(homePageAction)
}
@@ -835,6 +843,31 @@ private extension MasterFeedViewController {
return action
}
+ func getInfoAction(indexPath: IndexPath) -> UIAction? {
+ guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? Feed else {
+ return nil
+ }
+
+ let title = NSLocalizedString("Get Info", comment: "Get Info")
+ let action = UIAction(title: title, image: AppAssets.infoImage) { [weak self] action in
+ self?.coordinator.showFeedInspector(for: feed)
+ }
+ return action
+ }
+
+ func getInfoAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
+ guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? Feed else {
+ return nil
+ }
+
+ let title = NSLocalizedString("Get Info", comment: "Get Infor")
+ let action = UIAlertAction(title: title, style: .default) { [weak self] action in
+ self?.coordinator.showFeedInspector(for: feed)
+ completionHandler(true)
+ }
+ return action
+ }
+
func rename(indexPath: IndexPath) {
let name = (dataSource.itemIdentifier(for: indexPath)?.representedObject as? DisplayNameProvider)?.nameForDisplay ?? ""
diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift
index bfd9c45fd..9a845871d 100644
--- a/iOS/SceneCoordinator.swift
+++ b/iOS/SceneCoordinator.swift
@@ -800,6 +800,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
}
+ func showFeedInspector(for feed: Feed) {
+ rootSplitViewController.present(style: .formSheet) {
+ FeedInspectorView(viewModel: FeedInspectorView.ViewModel(feed: feed))
+ }
+ }
+
func showAdd(_ type: AddControllerType, initialFeed: String? = nil, initialFeedName: String? = nil) {
selectFeed(nil)
From a37e4b79207189a31eaa3d14e68fdac81469cff3 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Sat, 28 Sep 2019 12:11:33 -0500
Subject: [PATCH 008/189] Fixed feed separators for disclosure rows
---
iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift
index 1a89285bb..2df98fe02 100644
--- a/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift
+++ b/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift
@@ -13,7 +13,7 @@ struct MasterFeedTableViewCellLayout {
private static let editingControlIndent = CGFloat(integerLiteral: 40)
private static let imageSize = CGSize(width: 20, height: 20)
- private static let imageMarginRight = CGFloat(integerLiteral: 8)
+ private static let imageMarginRight = CGFloat(integerLiteral: 11)
private static let unreadCountMarginLeft = CGFloat(integerLiteral: 8)
private static let unreadCountMarginRight = CGFloat(integerLiteral: 16)
private static let disclosureButtonSize = CGSize(width: 44, height: 44)
@@ -58,7 +58,11 @@ struct MasterFeedTableViewCellLayout {
}
// Separator Insets
- separatorInsets = UIEdgeInsets(top: 0, left: rFavicon.maxX + MasterFeedTableViewCellLayout.imageMarginRight, bottom: 0, right: 0)
+ if shouldShowDisclosure {
+ separatorInsets = UIEdgeInsets(top: 0, left: MasterFeedTableViewCellLayout.disclosureButtonSize.width, bottom: 0, right: 0)
+ } else {
+ separatorInsets = UIEdgeInsets(top: 0, left: rFavicon.maxX + MasterFeedTableViewCellLayout.imageMarginRight, bottom: 0, right: 0)
+ }
// Unread Count
let unreadCountSize = unreadCountView.contentSize
From 4e78305a85efea4ac94f2459220da18186a0160f Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 28 Sep 2019 11:31:39 -0700
Subject: [PATCH 009/189] Update Slack group link to point to link that
redirects to the new Slack invitation page.
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 2f9cab550..020eb826d 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Here’s [How to Support NetNewsWire](Technotes/HowToSupportNetNewsWire.markdown
#### Community
-[Join the Slack group](https://join.slack.com/t/netnewswire/shared_invite/enQtNjM4MDA1MjQzMDkzLTNlNjBhOWVhYzdhYjA4ZWFhMzQ1MTUxYjU0NTE5ZGY0YzYwZWJhNjYwNTNmNTg2NjIwYWY4YzhlYzk5NmU3ZTc) to talk with other NetNewsWire users — and to help out, if you’d like to, by testing, coding, writing, providing feedback, or just helping us think things through. Everybody is welcome and encouraged to join.
+[Join the Slack group](https://ranchero.com/netnewswire/slack) to talk with other NetNewsWire users — and to help out, if you’d like to, by testing, coding, writing, providing feedback, or just helping us think things through. Everybody is welcome and encouraged to join.
Every community member is expected to abide by the code of conduct which is included in the [Contributing](CONTRIBUTING.md) page.
From 2b491217f3fbb33b3d03a962bc58eaf3c8f66881 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 28 Sep 2019 12:18:08 -0700
Subject: [PATCH 010/189] =?UTF-8?q?Create=20statusWithRow(=5F=20row:=20FMR?=
=?UTF-8?q?esultSet,=20articleID:=20String)=20=E2=80=94=C2=A0it=20allows?=
=?UTF-8?q?=20us=20to=20avoid=20pulling=20articleID=20from=20the=20row=20t?=
=?UTF-8?q?wice=20every=20time=20we=E2=80=99re=20creating=20a=20DatabaseAr?=
=?UTF-8?q?ticle.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Frameworks/ArticlesDatabase/ArticlesTable.swift | 13 ++++++-------
Frameworks/ArticlesDatabase/StatusesTable.swift | 8 ++++++--
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift
index 3ac4de861..a92354045 100644
--- a/Frameworks/ArticlesDatabase/ArticlesTable.swift
+++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift
@@ -475,16 +475,15 @@ private extension ArticlesTable {
func makeDatabaseArticles(with resultSet: FMResultSet) -> Set {
let articles = resultSet.mapToSet { (row) -> DatabaseArticle? in
- // The resultSet is a result of a JOIN query with the statuses table,
- // so we can get the statuses at the same time and avoid additional database lookups.
-
- guard let status = statusesTable.statusWithRow(resultSet) else {
- assertionFailure("Expected status.")
+ guard let articleID = row.string(forColumn: DatabaseKey.articleID) else {
+ assertionFailure("Expected articleID.")
return nil
}
- guard let articleID = row.string(forColumn: DatabaseKey.articleID) else {
- assertionFailure("Expected articleID.")
+ // The resultSet is a result of a JOIN query with the statuses table,
+ // so we can get the statuses at the same time and avoid additional database lookups.
+ guard let status = statusesTable.statusWithRow(resultSet, articleID: articleID) else {
+ assertionFailure("Expected status.")
return nil
}
guard let feedID = row.string(forColumn: DatabaseKey.feedID) else {
diff --git a/Frameworks/ArticlesDatabase/StatusesTable.swift b/Frameworks/ArticlesDatabase/StatusesTable.swift
index 0bb18377d..fa5a741ab 100644
--- a/Frameworks/ArticlesDatabase/StatusesTable.swift
+++ b/Frameworks/ArticlesDatabase/StatusesTable.swift
@@ -105,17 +105,21 @@ final class StatusesTable: DatabaseTable {
guard let articleID = row.string(forColumn: DatabaseKey.articleID) else {
return nil
}
+ return statusWithRow(row, articleID: articleID)
+ }
+
+ func statusWithRow(_ row: FMResultSet, articleID: String) ->ArticleStatus? {
if let cachedStatus = cache[articleID] {
return cachedStatus
}
-
+
guard let dateArrived = row.date(forColumn: DatabaseKey.dateArrived) else {
return nil
}
let articleStatus = ArticleStatus(articleID: articleID, dateArrived: dateArrived, row: row)
cache.addStatusIfNotCached(articleStatus)
-
+
return articleStatus
}
From 37c9818cad8511b9c507bbdafdca87a7e1bcafeb Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sat, 28 Sep 2019 13:51:33 -0700
Subject: [PATCH 011/189] =?UTF-8?q?Create=20and=20use=20a=20cache=20for=20?=
=?UTF-8?q?DatabaseArticle=20=E2=80=94=20this=20will=20make=20fetches=20fa?=
=?UTF-8?q?ster,=20since=20we=20can=20skip=20pulling=20the=20same=20data?=
=?UTF-8?q?=20from=20the=20database=20over=20and=20over.=20Articles=20in?=
=?UTF-8?q?=20the=20cache=20are=20removed=20when=20articles=20are=20update?=
=?UTF-8?q?d,=20so=20the=20cache=20is=20never=20stale.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ArticlesDatabase/ArticlesTable.swift | 23 ++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift
index a92354045..8f06afdc8 100644
--- a/Frameworks/ArticlesDatabase/ArticlesTable.swift
+++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift
@@ -20,6 +20,7 @@ final class ArticlesTable: DatabaseTable {
private let statusesTable: StatusesTable
private let authorsLookupTable: DatabaseLookupTable
private let attachmentsLookupTable: DatabaseLookupTable
+ private var databaseArticlesCache = [String: DatabaseArticle]()
private lazy var searchTable: SearchTable = {
return SearchTable(queue: queue, articlesTable: self)
@@ -480,6 +481,12 @@ private extension ArticlesTable {
return nil
}
+ // Articles are removed from the cache when they’re updated.
+ // See saveUpdatedArticles.
+ if let databaseArticle = databaseArticlesCache[articleID] {
+ return databaseArticle
+ }
+
// The resultSet is a result of a JOIN query with the statuses table,
// so we can get the statuses at the same time and avoid additional database lookups.
guard let status = statusesTable.statusWithRow(resultSet, articleID: articleID) else {
@@ -506,7 +513,9 @@ private extension ArticlesTable {
let datePublished = row.date(forColumn: DatabaseKey.datePublished)
let dateModified = row.date(forColumn: DatabaseKey.dateModified)
- return DatabaseArticle(articleID: articleID, feedID: feedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, status: status)
+ let databaseArticle = DatabaseArticle(articleID: articleID, feedID: feedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, status: status)
+ databaseArticlesCache[articleID] = databaseArticle
+ return databaseArticle
}
return articles
@@ -662,6 +671,7 @@ private extension ArticlesTable {
func saveUpdatedArticles(_ updatedArticles: Set, _ fetchedArticles: [String: Article], _ database: FMDatabase) {
+ removeArticlesFromDatabaseArticlesCache(updatedArticles)
saveUpdatedRelatedObjects(updatedArticles, fetchedArticles, database)
for updatedArticle in updatedArticles {
@@ -682,10 +692,17 @@ private extension ArticlesTable {
// Not unexpected. There may be no changes.
return
}
-
+
updateRowsWithDictionary(changesDictionary, whereKey: DatabaseKey.articleID, matches: updatedArticle.articleID, database: database)
}
-
+
+ func removeArticlesFromDatabaseArticlesCache(_ updatedArticles: Set) {
+ let articleIDs = updatedArticles.articleIDs()
+ for articleID in articleIDs {
+ databaseArticlesCache[articleID] = nil
+ }
+ }
+
func statusIndicatesArticleIsIgnorable(_ status: ArticleStatus) -> Bool {
// Ignorable articles: either userDeleted==1 or (not starred and arrival date > 4 months).
if status.userDeleted {
From c51f5f44e040ecbbd762c6a277d6d91372084f1e Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Sun, 29 Sep 2019 14:07:33 -0500
Subject: [PATCH 012/189] Add eclipses to truncated timeline text. Issue #1072
---
iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift
index 025c4ade9..b512da4f8 100644
--- a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift
+++ b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift
@@ -105,7 +105,7 @@ private extension MasterTimelineTableViewCell {
static func multiLineUILabel() -> UILabel {
let label = NonIntrinsicLabel()
label.numberOfLines = 0
- label.lineBreakMode = .byWordWrapping
+ label.lineBreakMode = .byTruncatingTail
label.allowsDefaultTighteningForTruncation = false
label.adjustsFontForContentSizeCategory = true
return label
From 1f26a91af9526391990379d148a2a1faf302d324 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Sun, 29 Sep 2019 15:53:50 -0500
Subject: [PATCH 013/189] Prevent scrolling while reloading cells Issue #1085
---
.../MasterTimelineViewController.swift | 12 ++++++++----
iOS/SceneCoordinator.swift | 2 +-
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift
index 78f5e919b..a74a96021 100644
--- a/iOS/MasterTimeline/MasterTimelineViewController.swift
+++ b/iOS/MasterTimeline/MasterTimelineViewController.swift
@@ -137,9 +137,13 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
// MARK: API
- func restoreSelectionIfNecessary() {
+ func restoreSelectionIfNecessary(adjustScroll: Bool) {
if let article = coordinator.currentArticle, let indexPath = dataSource.indexPath(for: article) {
- tableView.selectRowAndScrollIfNotVisible(at: indexPath, animated: false, deselect: coordinator.isRootSplitCollapsed)
+ if adjustScroll {
+ tableView.selectRowAndScrollIfNotVisible(at: indexPath, animated: false, deselect: coordinator.isRootSplitCollapsed)
+ } else {
+ tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
+ }
}
}
@@ -390,7 +394,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
var snapshot = dataSource.snapshot()
snapshot.reloadItems(articles)
dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
- self?.restoreSelectionIfNecessary()
+ self?.restoreSelectionIfNecessary(adjustScroll: false)
}
}
@@ -507,7 +511,7 @@ private extension MasterTimelineViewController {
snapshot.appendItems(coordinator.articles, toSection: 0)
dataSource.apply(snapshot, animatingDifferences: animate) { [weak self] in
- self?.restoreSelectionIfNecessary()
+ self?.restoreSelectionIfNecessary(adjustScroll: false)
completion?()
}
}
diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift
index 9a845871d..1a54b158a 100644
--- a/iOS/SceneCoordinator.swift
+++ b/iOS/SceneCoordinator.swift
@@ -1516,7 +1516,7 @@ private extension SceneCoordinator {
subSplitViewController!.showDetailViewController(navController, sender: self)
masterFeedViewController.restoreSelectionIfNecessary(adjustScroll: true)
- masterTimelineViewController!.restoreSelectionIfNecessary()
+ masterTimelineViewController!.restoreSelectionIfNecessary(adjustScroll: true)
// We made sure this was there above when we called configureDoubleSplit
return subSplitViewController!
From 0f362b7680727418ba24f04d3e1b204e60f248da Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 29 Sep 2019 17:01:20 -0700
Subject: [PATCH 014/189] Update RSDatabase.
---
submodules/RSDatabase | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/submodules/RSDatabase b/submodules/RSDatabase
index 9bb7e3745..807bb445f 160000
--- a/submodules/RSDatabase
+++ b/submodules/RSDatabase
@@ -1 +1 @@
-Subproject commit 9bb7e374518ad481c6da14b656d6676c02b33548
+Subproject commit 807bb445f0bc692cb6b9210f66db82a85efe5c59
From 3ffd461448236e0f198a013f0d887d8f586e99be Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 29 Sep 2019 17:02:10 -0700
Subject: [PATCH 015/189] Update RSDatabase.
---
submodules/RSDatabase | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/submodules/RSDatabase b/submodules/RSDatabase
index 807bb445f..9bb7e3745 160000
--- a/submodules/RSDatabase
+++ b/submodules/RSDatabase
@@ -1 +1 @@
-Subproject commit 807bb445f0bc692cb6b9210f66db82a85efe5c59
+Subproject commit 9bb7e374518ad481c6da14b656d6676c02b33548
From 6a9435a63ee3c2dfd67a76d9f9baeea59129b6cd Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 29 Sep 2019 17:03:00 -0700
Subject: [PATCH 016/189] Update RSDatabase.
---
submodules/RSDatabase | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/submodules/RSDatabase b/submodules/RSDatabase
index 9bb7e3745..807bb445f 160000
--- a/submodules/RSDatabase
+++ b/submodules/RSDatabase
@@ -1 +1 @@
-Subproject commit 9bb7e374518ad481c6da14b656d6676c02b33548
+Subproject commit 807bb445f0bc692cb6b9210f66db82a85efe5c59
From 9f126bfb8ff42b3bc8ce16f5a1373a3c8ce91964 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Sun, 29 Sep 2019 19:40:12 -0500
Subject: [PATCH 017/189] Add a background if the image is too dark and we are
in dark mode.
---
NetNewsWire.xcodeproj/project.pbxproj | 4 ++
.../Cell/MasterFeedTableViewCell.swift | 15 ++++++
iOS/UIKit Extensions/UIImage-Extensions.swift | 47 +++++++++++++++++++
3 files changed, 66 insertions(+)
create mode 100644 iOS/UIKit Extensions/UIImage-Extensions.swift
diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj
index 8bdb83bab..f3789f111 100644
--- a/NetNewsWire.xcodeproj/project.pbxproj
+++ b/NetNewsWire.xcodeproj/project.pbxproj
@@ -216,6 +216,7 @@
51FA73AA2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; };
51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; };
51FA73B72332D5F70090D516 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */; };
+ 51FD40C72341555A00880194 /* UIImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD40BD2341555600880194 /* UIImage-Extensions.swift */; };
55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */; };
55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; };
5F323809231DF9F000706F6B /* NNWTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F323808231DF9F000706F6B /* NNWTableViewCell.swift */; };
@@ -895,6 +896,7 @@
51FA73A62332BE880090D516 /* ExtractedArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtractedArticle.swift; sourceTree = ""; };
51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorConfig.swift; sourceTree = ""; };
51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; };
+ 51FD40BD2341555600880194 /* UIImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage-Extensions.swift"; sourceTree = ""; };
557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsReaderAPIAccountView.swift; sourceTree = ""; };
55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsReaderAPI.xib; sourceTree = ""; };
55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsReaderAPIWindowController.swift; sourceTree = ""; };
@@ -1332,6 +1334,7 @@
51934CC1230F5963006127BE /* ThemedNavigationController.swift */,
51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */,
51F85BF622749FA100C787DC /* UIFont-Extensions.swift */,
+ 51FD40BD2341555600880194 /* UIImage-Extensions.swift */,
512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */,
51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */,
);
@@ -2807,6 +2810,7 @@
51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */,
51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */,
5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */,
+ 51FD40C72341555A00880194 /* UIImage-Extensions.swift in Sources */,
5132293B23305D4C0033D4ED /* SettingsAboutView.swift in Sources */,
84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */,
51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */,
diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift
index 1322c8b72..25d481c2d 100644
--- a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift
+++ b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift
@@ -34,6 +34,21 @@ class MasterFeedTableViewCell : NNWTableViewCell {
var faviconImage: UIImage? {
didSet {
faviconImageView.image = faviconImage
+
+ if self.traitCollection.userInterfaceStyle == .dark {
+ DispatchQueue.global(qos: .background).async {
+ if self.faviconImage?.isDark() ?? false {
+ DispatchQueue.main.async {
+ self.faviconImageView.backgroundColor = AppAssets.avatarBackgroundColor
+ }
+ } else {
+ DispatchQueue.main.async {
+ self.faviconImageView.backgroundColor = nil
+ }
+ }
+ }
+ }
+
}
}
diff --git a/iOS/UIKit Extensions/UIImage-Extensions.swift b/iOS/UIKit Extensions/UIImage-Extensions.swift
new file mode 100644
index 000000000..a64d3965d
--- /dev/null
+++ b/iOS/UIKit Extensions/UIImage-Extensions.swift
@@ -0,0 +1,47 @@
+//
+// UIImage-Extensions.swift
+// NetNewsWire
+//
+// Created by Maurice Parker on 9/29/19.
+// Copyright © 2019 Ranchero Software. All rights reserved.
+//
+
+import UIKit
+
+extension CGImage {
+
+ func isDark() -> Bool {
+ guard let imageData = self.dataProvider?.data else { return false }
+ guard let ptr = CFDataGetBytePtr(imageData) else { return false }
+
+ let length = CFDataGetLength(imageData)
+ var visiblePixels = 0
+ var darkPixels = 0
+
+ for i in stride(from: 0, to: length, by: 4) {
+
+ let r = ptr[i]
+ let g = ptr[i + 1]
+ let b = ptr[i + 2]
+ let a = ptr[i + 3]
+ let luminance = (0.299 * Double(r) + 0.587 * Double(g) + 0.114 * Double(b))
+
+ if Double(a) > 0.0 {
+ visiblePixels += 1
+ if luminance < 50 {
+ darkPixels += 1
+ }
+ }
+
+ }
+
+ return Double(darkPixels) / Double(visiblePixels) > 0.4
+ }
+
+}
+
+extension UIImage {
+ func isDark() -> Bool {
+ return self.cgImage?.isDark() ?? false
+ }
+}
From b62a7afa10c828c84e1a2900399eb49a1b03d8fa Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 29 Sep 2019 22:34:31 -0700
Subject: [PATCH 018/189] =?UTF-8?q?Add=20keyboard=20shortcut=20for=20toggl?=
=?UTF-8?q?ing=20starred=20status=20=E2=80=94=C2=A0s=20key.=20Fix=20#875.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Mac/MainWindow/Keyboard/GlobalKeyboardShortcuts.plist | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Mac/MainWindow/Keyboard/GlobalKeyboardShortcuts.plist b/Mac/MainWindow/Keyboard/GlobalKeyboardShortcuts.plist
index 3f715052d..6218cd47e 100644
--- a/Mac/MainWindow/Keyboard/GlobalKeyboardShortcuts.plist
+++ b/Mac/MainWindow/Keyboard/GlobalKeyboardShortcuts.plist
@@ -108,5 +108,11 @@
action
goToNextSubscription:
+
+ key
+ s
+ action
+ toggleStarred:
+
From 27478b0f6f4c18caec206b7f53051a926145f7c2 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 29 Sep 2019 22:44:32 -0700
Subject: [PATCH 019/189] Make r and u both just toggle read status. Update
keyboard shortcuts HTML documentation to match.
---
Mac/MainWindow/Keyboard/GlobalKeyboardShortcuts.plist | 4 ++--
Mac/Resources/KeyboardShortcuts/KeyboardShortcuts.html | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Mac/MainWindow/Keyboard/GlobalKeyboardShortcuts.plist b/Mac/MainWindow/Keyboard/GlobalKeyboardShortcuts.plist
index 6218cd47e..32695e230 100644
--- a/Mac/MainWindow/Keyboard/GlobalKeyboardShortcuts.plist
+++ b/Mac/MainWindow/Keyboard/GlobalKeyboardShortcuts.plist
@@ -46,7 +46,7 @@
key
r
action
- markRead:
+ toggleRead:
key
@@ -76,7 +76,7 @@
key
u
action
- markUnread:
+ toggleRead:
key
diff --git a/Mac/Resources/KeyboardShortcuts/KeyboardShortcuts.html b/Mac/Resources/KeyboardShortcuts/KeyboardShortcuts.html
index 92c64a5ee..d44dd4880 100644
--- a/Mac/Resources/KeyboardShortcuts/KeyboardShortcuts.html
+++ b/Mac/Resources/KeyboardShortcuts/KeyboardShortcuts.html
@@ -51,12 +51,12 @@
Scroll or go to next unread space
Go to next unread n or +
- Mark as read r
+ Toggle read status r or u
Mark all as read k
Mark older articles as read o
Mark all as read, go to next unread l
Mark as unread, go to next unread m
- Mark as unread u
+ Toggle starred status s
Open in browser b or ⏎ or Enter
Previous subscription a
Next subscription z
From 098128fd0af60864bbff1e8f4bfb34390c5b644f Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Sun, 29 Sep 2019 22:46:40 -0700
Subject: [PATCH 020/189] Use new Slack group URL with Help menu command. Fix
#1087.
---
Mac/AppDelegate.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift
index eb122e939..581126f56 100644
--- a/Mac/AppDelegate.swift
+++ b/Mac/AppDelegate.swift
@@ -454,7 +454,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
}
@IBAction func openSlackGroup(_ sender: Any?) {
- Browser.open("https://join.slack.com/t/netnewswire/shared_invite/enQtNjM4MDA1MjQzMDkzLTNlNjBhOWVhYzdhYjA4ZWFhMzQ1MTUxYjU0NTE5ZGY0YzYwZWJhNjYwNTNmNTg2NjIwYWY4YzhlYzk5NmU3ZTc", inBackground: false)
+ Browser.open("https://ranchero.com/netnewswire/slack", inBackground: false)
}
@IBAction func openTechnotes(_ sender: Any?) {
From 8f53916a79695d39950d4f09e5a9400fd65f5d46 Mon Sep 17 00:00:00 2001
From: Kiel Gillard
Date: Mon, 30 Sep 2019 09:45:13 +1000
Subject: [PATCH 021/189] Make Account framework tests compile and pass.
---
.../AccountTests/AccountCredentialsTest.swift | 34 +++++++++++--------
.../AccountTests/AccountFeedSyncTest.swift | 4 +--
.../AccountFolderContentsSyncTest.swift | 6 ++--
.../AccountTests/AccountFolderSyncTest.swift | 6 ++--
.../Account/AccountTests/TestTransport.swift | 23 ++++++++++---
5 files changed, 45 insertions(+), 28 deletions(-)
diff --git a/Frameworks/Account/AccountTests/AccountCredentialsTest.swift b/Frameworks/Account/AccountTests/AccountCredentialsTest.swift
index d9e7ba91f..2d1f2a85d 100644
--- a/Frameworks/Account/AccountTests/AccountCredentialsTest.swift
+++ b/Frameworks/Account/AccountTests/AccountCredentialsTest.swift
@@ -26,12 +26,12 @@ class AccountCredentialsTest: XCTestCase {
// Make sure any left over from failed tests are gone
do {
- try account.removeBasicCredentials()
+ try account.removeCredentials(type: .basic)
} catch {
XCTFail(error.localizedDescription)
}
- var credentials: Credentials? = Credentials.basic(username: "maurice", password: "hardpasswd")
+ var credentials: Credentials? = Credentials(type: .basic, username: "maurice", secret: "hardpasswd")
// Store the credentials
do {
@@ -43,19 +43,21 @@ class AccountCredentialsTest: XCTestCase {
// Retrieve them
credentials = nil
do {
- credentials = try account.retrieveBasicCredentials()
+ credentials = try account.retrieveCredentials(type: .basic)
} catch {
XCTFail(error.localizedDescription)
}
- switch credentials! {
- case .basic(let username, let password):
- XCTAssertEqual("maurice", username)
- XCTAssertEqual("hardpasswd", password)
+ switch credentials!.type {
+ case .basic:
+ XCTAssertEqual("maurice", credentials?.username)
+ XCTAssertEqual("hardpasswd", credentials?.secret)
+ default:
+ XCTFail("Expected \(CredentialsType.basic), received \(credentials!.type)")
}
// Update them
- credentials = Credentials.basic(username: "maurice", password: "easypasswd")
+ credentials = Credentials(type: .basic, username: "maurice", secret: "easypasswd")
do {
try account.storeCredentials(credentials!)
} catch {
@@ -65,27 +67,29 @@ class AccountCredentialsTest: XCTestCase {
// Retrieve them again
credentials = nil
do {
- credentials = try account.retrieveBasicCredentials()
+ credentials = try account.retrieveCredentials(type: .basic)
} catch {
XCTFail(error.localizedDescription)
}
- switch credentials! {
- case .basic(let username, let password):
- XCTAssertEqual("maurice", username)
- XCTAssertEqual("easypasswd", password)
+ switch credentials!.type {
+ case .basic:
+ XCTAssertEqual("maurice", credentials?.username)
+ XCTAssertEqual("easypasswd", credentials?.secret)
+ default:
+ XCTFail("Expected \(CredentialsType.basic), received \(credentials!.type)")
}
// Delete them
do {
- try account.removeBasicCredentials()
+ try account.removeCredentials(type: .basic)
} catch {
XCTFail(error.localizedDescription)
}
// Make sure they are gone
do {
- try credentials = account.retrieveBasicCredentials()
+ try credentials = account.retrieveCredentials(type: .basic)
} catch {
XCTFail(error.localizedDescription)
}
diff --git a/Frameworks/Account/AccountTests/AccountFeedSyncTest.swift b/Frameworks/Account/AccountTests/AccountFeedSyncTest.swift
index 07f42ace5..45131c3dd 100644
--- a/Frameworks/Account/AccountTests/AccountFeedSyncTest.swift
+++ b/Frameworks/Account/AccountTests/AccountFeedSyncTest.swift
@@ -27,7 +27,7 @@ class AccountFeedSyncTest: XCTestCase {
// Test initial folders
let initialExpection = self.expectation(description: "Initial feeds")
- account.refreshAll() {
+ account.refreshAll() { _ in
initialExpection.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
@@ -44,7 +44,7 @@ class AccountFeedSyncTest: XCTestCase {
testTransport.testFiles["https://api.feedbin.com/v2/subscriptions.json"] = "subscriptions_add.json"
let addExpection = self.expectation(description: "Add feeds")
- account.refreshAll() {
+ account.refreshAll() { _ in
addExpection.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
diff --git a/Frameworks/Account/AccountTests/AccountFolderContentsSyncTest.swift b/Frameworks/Account/AccountTests/AccountFolderContentsSyncTest.swift
index 0be0ee639..c3ca12b3c 100644
--- a/Frameworks/Account/AccountTests/AccountFolderContentsSyncTest.swift
+++ b/Frameworks/Account/AccountTests/AccountFolderContentsSyncTest.swift
@@ -28,7 +28,7 @@ class AccountFolderContentsSyncTest: XCTestCase {
// Test initial folders
let initialExpection = self.expectation(description: "Initial contents")
- account.refreshAll() {
+ account.refreshAll() { _ in
initialExpection.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
@@ -41,7 +41,7 @@ class AccountFolderContentsSyncTest: XCTestCase {
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "taggings_add.json"
let addExpection = self.expectation(description: "Add contents")
- account.refreshAll() {
+ account.refreshAll() { _ in
addExpection.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
@@ -53,7 +53,7 @@ class AccountFolderContentsSyncTest: XCTestCase {
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "taggings_delete.json"
let deleteExpection = self.expectation(description: "Delete contents")
- account.refreshAll() {
+ account.refreshAll() { _ in
deleteExpection.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
diff --git a/Frameworks/Account/AccountTests/AccountFolderSyncTest.swift b/Frameworks/Account/AccountTests/AccountFolderSyncTest.swift
index e4d4c6cb8..09831a937 100644
--- a/Frameworks/Account/AccountTests/AccountFolderSyncTest.swift
+++ b/Frameworks/Account/AccountTests/AccountFolderSyncTest.swift
@@ -25,7 +25,7 @@ class AccountFolderSyncTest: XCTestCase {
// Test initial folders
let initialExpection = self.expectation(description: "Initial tags")
- account.refreshAll() {
+ account.refreshAll() { _ in
initialExpection.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
@@ -43,7 +43,7 @@ class AccountFolderSyncTest: XCTestCase {
testTransport.testFiles["https://api.feedbin.com/v2/tags.json"] = "tags_delete.json"
let deleteExpection = self.expectation(description: "Delete tags")
- account.refreshAll() {
+ account.refreshAll() { _ in
deleteExpection.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
@@ -62,7 +62,7 @@ class AccountFolderSyncTest: XCTestCase {
testTransport.testFiles["https://api.feedbin.com/v2/tags.json"] = "tags_add.json"
let addExpection = self.expectation(description: "Add tags")
- account.refreshAll() {
+ account.refreshAll() { _ in
addExpection.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
diff --git a/Frameworks/Account/AccountTests/TestTransport.swift b/Frameworks/Account/AccountTests/TestTransport.swift
index 781edc98b..1884c6256 100644
--- a/Frameworks/Account/AccountTests/TestTransport.swift
+++ b/Frameworks/Account/AccountTests/TestTransport.swift
@@ -16,30 +16,43 @@ final class TestTransport: Transport {
}
var testFiles = [String: String]()
+ var testStatusCodes = [String: Int]()
- func send(request: URLRequest, completion: @escaping (Result<(HTTPHeaders, Data?), Error>) -> Void) {
+ private func httpResponse(for request: URLRequest, statusCode: Int = 200) -> HTTPURLResponse {
+ guard let url = request.url else {
+ fatalError("Attempting to mock a http response for a request without a URL \(request).")
+ }
+ return HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: nil)!
+ }
+
+ func send(request: URLRequest, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void) {
guard let urlString = request.url?.absoluteString else {
completion(.failure(TestTransportError.invalidState))
return
}
+ let response = httpResponse(for: request, statusCode: testStatusCodes[urlString] ?? 200)
+
if let testFileName = testFiles[urlString] {
let testFileURL = Bundle(for: TestTransport.self).resourceURL!.appendingPathComponent(testFileName)
let data = try! Data(contentsOf: testFileURL)
DispatchQueue.global(qos: .background).async {
- completion(.success((HTTPHeaders(), data)))
+ completion(.success((response, data)))
}
} else {
DispatchQueue.global(qos: .background).async {
- completion(.success((HTTPHeaders(), nil)))
+ completion(.success((response, nil)))
}
}
}
- func send(request: URLRequest, payload: Data, completion: @escaping (Result<(HTTPHeaders, Data?), Error>) -> Void) {
-
+ func send(request: URLRequest, method: String, completion: @escaping (Result) -> Void) {
+ fatalError("Unimplemented.")
}
+ func send(request: URLRequest, method: String, payload: Data, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void) {
+ fatalError("Unimplemented.")
+ }
}
From 8d0bfd9b47bdd5a8d87d216dcab5c619dba97101 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 02:45:33 -0500
Subject: [PATCH 022/189] Make inspector add a background for small dark images
---
iOS/Inspector/FeedInspectorView.swift | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/iOS/Inspector/FeedInspectorView.swift b/iOS/Inspector/FeedInspectorView.swift
index 66d15d2f9..1b695a0bf 100644
--- a/iOS/Inspector/FeedInspectorView.swift
+++ b/iOS/Inspector/FeedInspectorView.swift
@@ -13,6 +13,7 @@ import Account
struct FeedInspectorView : View {
@ObservedObject var viewModel: ViewModel
+ @Environment(\.colorScheme) private var colorScheme: ColorScheme
@Environment(\.viewController) private var viewController: UIViewController?
var body: some View {
@@ -22,9 +23,24 @@ struct FeedInspectorView : View {
HStack {
Spacer()
if self.viewModel.image.size.width < 32 || self.viewModel.image.size.height < 32 {
- Image(uiImage: self.viewModel.image).resizable().frame(width: 24.0, height: 24.0).cornerRadius(2.0)
+ if colorScheme == .dark && self.viewModel.image.isDark() {
+ ZStack {
+ Color(AppAssets.avatarBackgroundColor)
+ Image(uiImage: self.viewModel.image).resizable()
+ }
+ .frame(width: 24.0, height: 24.0)
+ .cornerRadius(2.0)
+ } else {
+ Image(uiImage: self.viewModel.image)
+ .resizable()
+ .frame(width: 24.0, height: 24.0)
+ .cornerRadius(2.0)
+ }
} else {
- Image(uiImage: self.viewModel.image).resizable().frame(width: 48.0, height: 48.0).cornerRadius(5.0)
+ Image(uiImage: self.viewModel.image)
+ .resizable()
+ .frame(width: 48.0, height: 48.0)
+ .cornerRadius(5.0)
}
Spacer()
}) {
From 37418471bc3f25fc709a8afdbc6602f02156c89e Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 02:48:51 -0500
Subject: [PATCH 023/189] Give large images a background in darkmode when they
are dark
---
iOS/Inspector/FeedInspectorView.swift | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/iOS/Inspector/FeedInspectorView.swift b/iOS/Inspector/FeedInspectorView.swift
index 1b695a0bf..79b22175a 100644
--- a/iOS/Inspector/FeedInspectorView.swift
+++ b/iOS/Inspector/FeedInspectorView.swift
@@ -24,10 +24,9 @@ struct FeedInspectorView : View {
Spacer()
if self.viewModel.image.size.width < 32 || self.viewModel.image.size.height < 32 {
if colorScheme == .dark && self.viewModel.image.isDark() {
- ZStack {
- Color(AppAssets.avatarBackgroundColor)
- Image(uiImage: self.viewModel.image).resizable()
- }
+ Image(uiImage: self.viewModel.image)
+ .resizable()
+ .background(Color(AppAssets.avatarBackgroundColor))
.frame(width: 24.0, height: 24.0)
.cornerRadius(2.0)
} else {
@@ -37,10 +36,18 @@ struct FeedInspectorView : View {
.cornerRadius(2.0)
}
} else {
- Image(uiImage: self.viewModel.image)
- .resizable()
- .frame(width: 48.0, height: 48.0)
- .cornerRadius(5.0)
+ if colorScheme == .dark && self.viewModel.image.isDark() {
+ Image(uiImage: self.viewModel.image)
+ .resizable()
+ .background(Color(AppAssets.avatarBackgroundColor))
+ .frame(width: 48.0, height: 48.0)
+ .cornerRadius(5.0)
+ } else {
+ Image(uiImage: self.viewModel.image)
+ .resizable()
+ .frame(width: 48.0, height: 48.0)
+ .cornerRadius(5.0)
+ }
}
Spacer()
}) {
From bacff92434aa21f076f589e15505b682409ad7a7 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 02:54:19 -0500
Subject: [PATCH 024/189] Make timeline favicon have a background in darkmode
when the favicon is too dark
---
iOS/MasterTimeline/MasterTimelineViewController.swift | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift
index a74a96021..aac438b09 100644
--- a/iOS/MasterTimeline/MasterTimelineViewController.swift
+++ b/iOS/MasterTimeline/MasterTimelineViewController.swift
@@ -472,7 +472,12 @@ private extension MasterTimelineViewController {
if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView {
self.titleView = titleView
+
titleView.imageView.image = coordinator.timelineFavicon
+ if traitCollection.userInterfaceStyle == .dark && titleView.imageView.image?.isDark() ?? false {
+ titleView.imageView.backgroundColor = AppAssets.avatarBackgroundColor
+ }
+
titleView.label.text = coordinator.timelineName
if coordinator.timelineFetcher is Feed {
From 5717c84067a5a1fa0323a3a83910355ce0f243d5 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 02:59:27 -0500
Subject: [PATCH 025/189] Make generated favicons a little bit brighter
---
Shared/Favicons/ColorHash.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Shared/Favicons/ColorHash.swift b/Shared/Favicons/ColorHash.swift
index 650a70b76..b275d0c59 100644
--- a/Shared/Favicons/ColorHash.swift
+++ b/Shared/Favicons/ColorHash.swift
@@ -18,7 +18,7 @@ import AppKit
public class ColorHash {
- public static let defaultLS = [CGFloat(0.35), CGFloat(0.5), CGFloat(0.65)]
+ public static let defaultLS = [CGFloat(0.45), CGFloat(0.6), CGFloat(0.75)]
let seed = CGFloat(131.0)
let seed2 = CGFloat(137.0)
let maxSafeInteger = 9007199254740991.0 / CGFloat(137.0)
From 7316ceed9d781ade6f10c4daa36e08b1829aef87 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 08:07:12 -0500
Subject: [PATCH 026/189] Use newish keyboard modifier for username/email
fields. Issue #1065
---
iOS/Settings/Account/SettingsFeedbinAccountView.swift | 4 +++-
iOS/Settings/Account/SettingsReaderAPIAccountView.swift | 4 +++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/iOS/Settings/Account/SettingsFeedbinAccountView.swift b/iOS/Settings/Account/SettingsFeedbinAccountView.swift
index 1dad3a572..511318a81 100644
--- a/iOS/Settings/Account/SettingsFeedbinAccountView.swift
+++ b/iOS/Settings/Account/SettingsFeedbinAccountView.swift
@@ -28,7 +28,9 @@ struct SettingsFeedbinAccountView : View {
Spacer()
}
) {
- TextField("Email", text: $viewModel.email).textContentType(.emailAddress)
+ TextField("Email", text: $viewModel.email)
+ .keyboardType(.emailAddress)
+ .textContentType(.emailAddress)
SecureField("Password", text: $viewModel.password)
}
Section(footer:
diff --git a/iOS/Settings/Account/SettingsReaderAPIAccountView.swift b/iOS/Settings/Account/SettingsReaderAPIAccountView.swift
index c091fbdc8..9e4218e9a 100644
--- a/iOS/Settings/Account/SettingsReaderAPIAccountView.swift
+++ b/iOS/Settings/Account/SettingsReaderAPIAccountView.swift
@@ -29,7 +29,9 @@ struct SettingsReaderAPIAccountView : View {
Spacer()
}
) {
- TextField("Email", text: $viewModel.email).textContentType(.username)
+ TextField("Email", text: $viewModel.email)
+ .keyboardType(.emailAddress)
+ .textContentType(.emailAddress)
SecureField("Password", text: $viewModel.password)
TextField("API URL:", text: $viewModel.apiURL).textContentType(.URL)
}
From d59b5016debf7c12b940b70486682b76a6ed5497 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 11:42:11 -0500
Subject: [PATCH 027/189] Fix feed inspector name editing
---
iOS/Inspector/FeedInspectorView.swift | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/iOS/Inspector/FeedInspectorView.swift b/iOS/Inspector/FeedInspectorView.swift
index 79b22175a..95bc6b080 100644
--- a/iOS/Inspector/FeedInspectorView.swift
+++ b/iOS/Inspector/FeedInspectorView.swift
@@ -96,11 +96,15 @@ struct FeedInspectorView : View {
var name: String {
get {
- return feed.editedName ?? feed.name ?? ""
+ return feed.editedName ?? ""
}
set {
objectWillChange.send()
- feed.editedName = newValue
+ if newValue.isEmpty {
+ feed.editedName = nil
+ } else {
+ feed.editedName = newValue
+ }
}
}
From a7bee3a6d51bdfe40896b62c8159574bd2454221 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 12:36:25 -0500
Subject: [PATCH 028/189] Animate navigation for First Unread. Issue #1071
---
iOS/SceneCoordinator.swift | 37 +++++++++++++++++++++----------------
1 file changed, 21 insertions(+), 16 deletions(-)
diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift
index 1a54b158a..61da29f00 100644
--- a/iOS/SceneCoordinator.swift
+++ b/iOS/SceneCoordinator.swift
@@ -556,11 +556,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
func selectArticle(_ article: Article?, automated: Bool = true) {
guard article != currentArticle else { return }
- articleExtractor?.cancel()
- articleExtractor = nil
- isShowingExtractedArticle = false
- articleViewController?.articleExtractorButtonState = .off
-
+ stopArticleExtractor()
currentArticle = article
activityManager.reading(currentArticle)
@@ -576,10 +572,13 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
return
}
+ let currentArticleViewController: ArticleViewController
if articleViewController == nil {
- let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self)
- articleViewController.coordinator = self
- installArticleController(articleViewController, automated: automated)
+ currentArticleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self)
+ currentArticleViewController.coordinator = self
+ installArticleController(currentArticleViewController, automated: automated)
+ } else {
+ currentArticleViewController = articleViewController!
}
if automated {
@@ -588,9 +587,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
if article!.feed?.isArticleExtractorAlwaysOn ?? false {
startArticleExtractorForCurrentLink()
- articleViewController?.state = .loading
+ currentArticleViewController.state = .loading
} else {
- articleViewController?.state = .article(article!)
+ currentArticleViewController.state = .article(article!)
}
markArticles(Set([article!]), statusKey: .read, flag: true)
@@ -828,10 +827,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
guard articleExtractor?.state != .processing else {
- articleExtractor?.cancel()
- articleExtractor = nil
- isShowingExtractedArticle = false
- articleViewController?.articleExtractorButtonState = .off
+ stopArticleExtractor()
articleViewController?.state = .article(article)
return
}
@@ -933,7 +929,9 @@ extension SceneCoordinator: UINavigationControllerDelegate {
// If we are using a phone and navigate away from the detail, clear up the article resources (including activity)
if viewController === masterTimelineViewController && !isThreePanelMode && rootSplitViewController.isCollapsed {
- selectArticle(nil)
+ stopArticleExtractor()
+ currentArticle = nil
+ activityManager.invalidateReading()
}
}
@@ -1180,7 +1178,7 @@ private extension SceneCoordinator {
for i in startingRow..(), animate: true)
From 2c3f665b582f204dab83c92bb5fd7b8d979fcfa0 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 13:32:54 -0500
Subject: [PATCH 029/189] Add accessibility labels to custom disclosure button
---
iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift | 2 ++
1 file changed, 2 insertions(+)
diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift
index 25d481c2d..5f684ce8f 100644
--- a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift
+++ b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift
@@ -117,8 +117,10 @@ class MasterFeedTableViewCell : NNWTableViewCell {
UIView.animate(withDuration: duration) {
if self.isDisclosureExpanded {
+ self.disclosureButton?.accessibilityLabel = NSLocalizedString("Collapse Folder", comment: "Collapse Folder")
self.disclosureButton?.imageView?.transform = CGAffineTransform(rotationAngle: 1.570796)
} else {
+ self.disclosureButton?.accessibilityLabel = NSLocalizedString("Expand Folder", comment: "Expand Folder")
self.disclosureButton?.imageView?.transform = CGAffineTransform(rotationAngle: 0)
}
}
From ed2257a4f4ed04f49db2c08fc551aad1232e28d3 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 14:04:18 -0500
Subject: [PATCH 030/189] Reenable background iOS screenshooting
---
iOS/RootSplitViewController.swift | 4 +---
iOS/SceneCoordinator.swift | 2 +-
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift
index 63a1965ea..89fb623f3 100644
--- a/iOS/RootSplitViewController.swift
+++ b/iOS/RootSplitViewController.swift
@@ -15,9 +15,7 @@ class RootSplitViewController: UISplitViewController {
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animate(alongsideTransition: { [weak self] context in
- if UIApplication.shared.applicationState != .background {
- self?.coordinator.configureThreePanelMode(for: size)
- }
+ self?.coordinator.configureThreePanelMode(for: size)
})
}
diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift
index 61da29f00..3111419d1 100644
--- a/iOS/SceneCoordinator.swift
+++ b/iOS/SceneCoordinator.swift
@@ -910,7 +910,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
extension SceneCoordinator: UISplitViewControllerDelegate {
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
- return masterTimelineViewController == nil
+ return currentArticle == nil
}
}
From 1613a374d2d011a72c92ebe14ff256e96aba9ac0 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 30 Sep 2019 13:33:52 -0700
Subject: [PATCH 031/189] Update version number.
---
xcconfig/NetNewsWire_target.xcconfig | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/xcconfig/NetNewsWire_target.xcconfig b/xcconfig/NetNewsWire_target.xcconfig
index 13b3e20ce..467010cfa 100644
--- a/xcconfig/NetNewsWire_target.xcconfig
+++ b/xcconfig/NetNewsWire_target.xcconfig
@@ -29,8 +29,8 @@ PROVISIONING_PROFILE_SPECIFIER =
#include? "../../SharedXcodeSettings/DeveloperSettings.xcconfig"
// High Level Settings common to both the Mac application and any extensions we bundle with it
-MARKETING_VERSION = 5.0.2
-CURRENT_PROJECT_VERSION = 2615
+MARKETING_VERSION = 5.0.3b1
+CURRENT_PROJECT_VERSION = 2616
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
COMBINE_HIDPI_IMAGES = YES
From 6ad1a21a74e693c0c665bf546ea6e09a155217a9 Mon Sep 17 00:00:00 2001
From: Brent Simmons
Date: Mon, 30 Sep 2019 14:32:18 -0700
Subject: [PATCH 032/189] Update appcast.
---
Appcasts/netnewswire-beta.xml | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/Appcasts/netnewswire-beta.xml b/Appcasts/netnewswire-beta.xml
index 105aefd2a..e270c3370 100755
--- a/Appcasts/netnewswire-beta.xml
+++ b/Appcasts/netnewswire-beta.xml
@@ -6,6 +6,32 @@
Most recent NetNewsWire changes with links to updates.
en
+ -
+
NetNewsWire 5.0.3b1
+ Performance enhancement: fetching articles from the database is faster, and sometimes much faster.
+
+Performance enhancement: syncing could block the main thread more than it should. We moved JSON decoding to a background thread, which fixes this. This is particularly noticeable during an initial sync.
+
+Keyboard shortcuts: the 's' key toggles starred status. The 'r' and 'u' keys now both toggle read status (instead of setting read and unread status, respectively).
+
+Articles view: articles where the feed icon is quite large would be slow to render — now they render as fast as other articles.
+
+Articles view: a bug where keyboard shortcuts wouldn’t work after giving the articles view focus has been fixed.
+
+Articles view: YouTube videos could end up small. Fixed.
+
+Articles view: fixed a bug scaling images to fit in the view.
+
+Feedbin syncing: fixed a bug where renaming a tag on the Feedbin site would result in feeds in NNW ending up at the top level.
+
+Help menu: fixed the expired Slack link.
+ ]]>
+ Mon, 30 Sep 2019 13:56:00 -0700
+
+ 10.14.4
+
+
-
NetNewsWire 5.0.2
Date: Mon, 30 Sep 2019 20:01:02 -0500
Subject: [PATCH 033/189] Add unread count to the timeline
---
NetNewsWire.xcodeproj/project.pbxproj | 4 +++
.../Cell/MasterFeedUnreadCountView.swift | 23 +++++++-----
.../MasterTimelineTitleView.swift | 1 +
.../MasterTimelineTitleView.xib | 13 +++++--
.../MasterTimelineUnreadCountView.swift | 35 +++++++++++++++++++
.../MasterTimelineViewController.swift | 10 +++++-
6 files changed, 74 insertions(+), 12 deletions(-)
create mode 100644 iOS/MasterTimeline/MasterTimelineUnreadCountView.swift
diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj
index f3789f111..9b35dae91 100644
--- a/NetNewsWire.xcodeproj/project.pbxproj
+++ b/NetNewsWire.xcodeproj/project.pbxproj
@@ -217,6 +217,7 @@
51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; };
51FA73B72332D5F70090D516 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */; };
51FD40C72341555A00880194 /* UIImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD40BD2341555600880194 /* UIImage-Extensions.swift */; };
+ 51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */; };
55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */; };
55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; };
5F323809231DF9F000706F6B /* NNWTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F323808231DF9F000706F6B /* NNWTableViewCell.swift */; };
@@ -897,6 +898,7 @@
51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorConfig.swift; sourceTree = ""; };
51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; };
51FD40BD2341555600880194 /* UIImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage-Extensions.swift"; sourceTree = ""; };
+ 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineUnreadCountView.swift; sourceTree = ""; };
557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsReaderAPIAccountView.swift; sourceTree = ""; };
55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsReaderAPI.xib; sourceTree = ""; };
55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsReaderAPIWindowController.swift; sourceTree = ""; };
@@ -1369,6 +1371,7 @@
51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */,
5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */,
5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */,
+ 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */,
51C4526F2265091600C03939 /* Cell */,
);
path = MasterTimeline;
@@ -2791,6 +2794,7 @@
517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */,
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */,
+ 51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */,
5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */,
51AF45E123246731001742EF /* SettingsAccountLabelView.swift in Sources */,
51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */,
diff --git a/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift b/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift
index fdddb629a..09b1d5d69 100644
--- a/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift
+++ b/iOS/MasterFeed/Cell/MasterFeedUnreadCountView.swift
@@ -10,19 +10,26 @@ import UIKit
class MasterFeedUnreadCountView : UIView {
- private let padding = UIEdgeInsets(top: 1.0, left: 7.0, bottom: 1.0, right: 7.0)
- private let cornerRadius = 8.0
- private let bgColor = UIColor.darkGray
- private let textColor = UIColor.white
- private var textAttributes: [NSAttributedString.Key: AnyObject] {
+ var padding: UIEdgeInsets {
+ return UIEdgeInsets(top: 1.0, left: 7.0, bottom: 1.0, right: 7.0)
+ }
+
+ let cornerRadius = 8.0
+ let bgColor = UIColor.darkGray
+ var textColor: UIColor {
+ return UIColor.white
+ }
+
+ var textAttributes: [NSAttributedString.Key: AnyObject] {
let textFont = UIFont.preferredFont(forTextStyle: .caption1)
return [NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.font: textFont, NSAttributedString.Key.kern: NSNull()]
}
- private var textSizeCache = [Int: CGSize]()
+ var textSizeCache = [Int: CGSize]()
var unreadCount = 0 {
didSet {
contentSizeIsValid = false
+ invalidateIntrinsicContentSize()
setNeedsDisplay()
}
}
@@ -69,7 +76,7 @@ class MasterFeedUnreadCountView : UIView {
return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
}
- private func textSize() -> CGSize {
+ func textSize() -> CGSize {
if unreadCount < 1 {
return CGSize.zero
@@ -88,7 +95,7 @@ class MasterFeedUnreadCountView : UIView {
}
- private func textRect() -> CGRect {
+ func textRect() -> CGRect {
let size = textSize()
var r = CGRect.zero
diff --git a/iOS/MasterTimeline/MasterTimelineTitleView.swift b/iOS/MasterTimeline/MasterTimelineTitleView.swift
index 2861ce634..03e68a1af 100644
--- a/iOS/MasterTimeline/MasterTimelineTitleView.swift
+++ b/iOS/MasterTimeline/MasterTimelineTitleView.swift
@@ -20,5 +20,6 @@ class MasterTimelineTitleView: UIView {
}
@IBOutlet weak var label: UILabel!
+ @IBOutlet weak var unreadCountView: MasterTimelineUnreadCountView!
}
diff --git a/iOS/MasterTimeline/MasterTimelineTitleView.xib b/iOS/MasterTimeline/MasterTimelineTitleView.xib
index 5abe26c5a..ad66ffdef 100644
--- a/iOS/MasterTimeline/MasterTimelineTitleView.xib
+++ b/iOS/MasterTimeline/MasterTimelineTitleView.xib
@@ -20,20 +20,26 @@
-
-
+
+
+
+
+
+
+
+
+
-
@@ -42,6 +48,7 @@
+
diff --git a/iOS/MasterTimeline/MasterTimelineUnreadCountView.swift b/iOS/MasterTimeline/MasterTimelineUnreadCountView.swift
new file mode 100644
index 000000000..a79a79af9
--- /dev/null
+++ b/iOS/MasterTimeline/MasterTimelineUnreadCountView.swift
@@ -0,0 +1,35 @@
+//
+// MasterTimelineUnreadCountView.swift
+// NetNewsWire-iOS
+//
+// Created by Maurice Parker on 9/30/19.
+// Copyright © 2019 Ranchero Software. All rights reserved.
+//
+
+import UIKit
+
+class MasterTimelineUnreadCountView: MasterFeedUnreadCountView {
+
+ override var textColor: UIColor {
+ return UIColor.systemBackground
+ }
+
+ override var intrinsicContentSize: CGSize {
+ return contentSize
+ }
+
+ override func draw(_ dirtyRect: CGRect) {
+
+ let cornerRadii = CGSize(width: cornerRadius, height: cornerRadius)
+ let rect = CGRect(x: 1, y: 1, width: bounds.width - 2, height: bounds.height - 2)
+ let path = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: cornerRadii)
+ AppAssets.primaryAccentColor.setFill()
+ path.fill()
+
+ if unreadCount > 0 {
+ unreadCountString.draw(at: textRect().origin, withAttributes: textAttributes)
+ }
+
+ }
+
+}
diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift
index aac438b09..84eda2f49 100644
--- a/iOS/MasterTimeline/MasterTimelineViewController.swift
+++ b/iOS/MasterTimeline/MasterTimelineViewController.swift
@@ -479,7 +479,8 @@ private extension MasterTimelineViewController {
}
titleView.label.text = coordinator.timelineName
-
+ updateTitleUnreadCount()
+
if coordinator.timelineFetcher is Feed {
titleView.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
let tap = UITapGestureRecognizer(target: self, action:#selector(showFeedInspector(_:)))
@@ -500,10 +501,17 @@ private extension MasterTimelineViewController {
}
func updateUI() {
+ updateTitleUnreadCount()
markAllAsReadButton.isEnabled = coordinator.isTimelineUnreadAvailable
firstUnreadButton.isEnabled = coordinator.isTimelineUnreadAvailable
}
+ func updateTitleUnreadCount() {
+ if let unreadCountProvider = coordinator.timelineFetcher as? UnreadCountProvider {
+ titleView?.unreadCountView.unreadCount = unreadCountProvider.unreadCount
+ }
+ }
+
func updateProgressIndicatorIfNeeded() {
if !coordinator.isThreePanelMode {
navigationController?.updateAccountRefreshProgressIndicator()
From c56c76e557d5febc03f0cdbc8be7eecd7a1789dd Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 23:18:27 -0500
Subject: [PATCH 034/189] Fixed ORGANIZATION_IDENTIFIER in xcconfig
---
xcconfig/NetNewsWire_iOSapp_target.xcconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/xcconfig/NetNewsWire_iOSapp_target.xcconfig b/xcconfig/NetNewsWire_iOSapp_target.xcconfig
index 7d67a834f..71a35cbf7 100644
--- a/xcconfig/NetNewsWire_iOSapp_target.xcconfig
+++ b/xcconfig/NetNewsWire_iOSapp_target.xcconfig
@@ -1,7 +1,7 @@
CODE_SIGN_IDENTITY = Developer ID Application
DEVELOPMENT_TEAM = DY2XQRVWN9
CODE_SIGN_STYLE = Automatic
-BUNDLE_ROOT = com.ranchero
+ORGANIZATION_IDENTIFIER = com.ranchero
PROVISIONING_PROFILE_SPECIFIER =
// developers can locally override the Xcode settings for code signing
From 704d7f4fefcd291b1aefbb8895ffb77dbd60a440 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 23:25:29 -0500
Subject: [PATCH 035/189] Update default CODE_SIGNING_AUTHORITY
---
xcconfig/NetNewsWire_iOSapp_target.xcconfig | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/xcconfig/NetNewsWire_iOSapp_target.xcconfig b/xcconfig/NetNewsWire_iOSapp_target.xcconfig
index 71a35cbf7..a42e9e012 100644
--- a/xcconfig/NetNewsWire_iOSapp_target.xcconfig
+++ b/xcconfig/NetNewsWire_iOSapp_target.xcconfig
@@ -1,4 +1,5 @@
-CODE_SIGN_IDENTITY = Developer ID Application
+CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer
+CODE_SIGN_IDENTITY[sdk=iphonesimulator*] = iPhone Developer
DEVELOPMENT_TEAM = DY2XQRVWN9
CODE_SIGN_STYLE = Automatic
ORGANIZATION_IDENTIFIER = com.ranchero
From fdfaa7416a38a4c12857f4c59bd8b5652dd286d3 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Mon, 30 Sep 2019 23:29:35 -0500
Subject: [PATCH 036/189] Update team to be correct for project owner
---
xcconfig/NetNewsWire_iOSapp_target.xcconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/xcconfig/NetNewsWire_iOSapp_target.xcconfig b/xcconfig/NetNewsWire_iOSapp_target.xcconfig
index a42e9e012..4100f71a8 100644
--- a/xcconfig/NetNewsWire_iOSapp_target.xcconfig
+++ b/xcconfig/NetNewsWire_iOSapp_target.xcconfig
@@ -1,6 +1,6 @@
CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer
CODE_SIGN_IDENTITY[sdk=iphonesimulator*] = iPhone Developer
-DEVELOPMENT_TEAM = DY2XQRVWN9
+DEVELOPMENT_TEAM = M8L2WTLA8W
CODE_SIGN_STYLE = Automatic
ORGANIZATION_IDENTIFIER = com.ranchero
PROVISIONING_PROFILE_SPECIFIER =
From 4e6e61842ab5cab8f9ba6d878e627dcce8f76867 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Tue, 1 Oct 2019 03:51:05 -0500
Subject: [PATCH 037/189] Make sure the web view has been initialized before
deallocating it.
---
iOS/Article/ArticleViewController.swift | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift
index 72ea917dd..374657506 100644
--- a/iOS/Article/ArticleViewController.swift
+++ b/iOS/Article/ArticleViewController.swift
@@ -77,9 +77,11 @@ class ArticleViewController: UIViewController {
}
deinit {
- webView.removeFromSuperview()
- ArticleViewControllerWebViewProvider.shared.enqueueWebView(webView)
- webView = nil
+ if webView != nil {
+ webView.removeFromSuperview()
+ ArticleViewControllerWebViewProvider.shared.enqueueWebView(webView)
+ webView = nil
+ }
}
override func viewDidLoad() {
From 14e808971d87890c13530080ad0cb542baf95254 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Tue, 1 Oct 2019 03:51:48 -0500
Subject: [PATCH 038/189] Don't clear the current article activities if we have
a article view controller push pending.
---
iOS/SceneCoordinator.swift | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift
index 3111419d1..74bf7fe83 100644
--- a/iOS/SceneCoordinator.swift
+++ b/iOS/SceneCoordinator.swift
@@ -65,6 +65,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
private var lastSearchScope: SearchScope? = nil
private var isSearching: Bool = false
private var searchArticleIds: Set? = nil
+ private var isArticleViewControllerPending = false
private(set) var sortDirection = AppDefaults.timelineSortDirection {
didSet {
@@ -925,13 +926,22 @@ extension SceneCoordinator: UINavigationControllerDelegate {
if viewController === masterFeedViewController && !isThreePanelMode {
activityManager.invalidateCurrentActivities()
selectFeed(nil)
+ return
}
- // If we are using a phone and navigate away from the detail, clear up the article resources (including activity)
- if viewController === masterTimelineViewController && !isThreePanelMode && rootSplitViewController.isCollapsed {
+ // If we are using a phone and navigate away from the detail, clear up the article resources (including activity).
+ // Don't clear it if we have pushed an ArticleViewController, but don't yet see it on the navigation stack.
+ // This happens when we are going to the next unread and we need to grab another timeline to continue. The
+ // ArticleViewController will be pushed, but we will breifly show the Timeline. Don't clear things out when that happens.
+ if viewController === masterTimelineViewController && !isThreePanelMode && rootSplitViewController.isCollapsed && !isArticleViewControllerPending {
stopArticleExtractor()
currentArticle = nil
activityManager.invalidateReading()
+ return
+ }
+
+ if viewController is ArticleViewController {
+ isArticleViewControllerPending = false
}
}
@@ -1434,6 +1444,8 @@ private extension SceneCoordinator {
func installArticleController(_ articleController: UIViewController, automated: Bool) {
+ isArticleViewControllerPending = true
+
if let subSplit = subSplitViewController {
let controller = addNavControllerIfNecessary(articleController, showButton: false)
subSplit.showDetailViewController(controller, sender: self)
From 66d9e882adca39a05c80566f796aec376e012a7b Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Tue, 1 Oct 2019 04:31:42 -0500
Subject: [PATCH 039/189] Don't track back navigation when the app is in the
background
---
iOS/SceneCoordinator.swift | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift
index 74bf7fe83..a7803867c 100644
--- a/iOS/SceneCoordinator.swift
+++ b/iOS/SceneCoordinator.swift
@@ -922,6 +922,10 @@ extension SceneCoordinator: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
+ if UIApplication.shared.applicationState == .background {
+ return
+ }
+
// If we are showing the Feeds and only the feeds start clearing stuff
if viewController === masterFeedViewController && !isThreePanelMode {
activityManager.invalidateCurrentActivities()
From f4d58bd7a759e571b6be5fead9d8ef412f6af194 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Tue, 1 Oct 2019 09:49:07 -0500
Subject: [PATCH 040/189] Animate the timeline unread count indicator
---
iOS/MasterTimeline/MasterTimelineViewController.swift | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift
index 84eda2f49..ccc773a17 100644
--- a/iOS/MasterTimeline/MasterTimelineViewController.swift
+++ b/iOS/MasterTimeline/MasterTimelineViewController.swift
@@ -508,7 +508,11 @@ private extension MasterTimelineViewController {
func updateTitleUnreadCount() {
if let unreadCountProvider = coordinator.timelineFetcher as? UnreadCountProvider {
- titleView?.unreadCountView.unreadCount = unreadCountProvider.unreadCount
+ UIView.animate(withDuration: 0.3) {
+ self.titleView?.unreadCountView.unreadCount = unreadCountProvider.unreadCount
+ self.titleView?.setNeedsLayout()
+ self.titleView?.layoutIfNeeded()
+ }
}
}
From 608da70e8e9b5942bfe3ab49569e7c4e1cb24a6f Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Tue, 1 Oct 2019 11:09:46 -0500
Subject: [PATCH 041/189] Launch Safari when web preview is tapped. Issue
#1090
---
iOS/Article/ArticleViewController.swift | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift
index 374657506..3d7ae5b8f 100644
--- a/iOS/Article/ArticleViewController.swift
+++ b/iOS/Article/ArticleViewController.swift
@@ -101,6 +101,7 @@ class ArticleViewController: UIViewController {
self.webView = webView
self.webViewContainer.addChildAndPin(webView)
webView.navigationDelegate = self
+ webView.uiDelegate = self
// Even though page.html should be loaded into this webview, we have to do it again
// to work around this bug: http://www.openradar.me/22855188
@@ -325,6 +326,17 @@ extension ArticleViewController: WKNavigationDelegate {
}
+// MARK: WKUIDelegate
+
+extension ArticleViewController: WKUIDelegate {
+ func webView(_ webView: WKWebView, contextMenuForElement elementInfo: WKContextMenuElementInfo, willCommitWithAnimator animator: UIContextMenuInteractionCommitAnimating) {
+ // We need to have at least an unimplemented WKUIDelegate assigned to the WKWebView. This makes the
+ // link preview launch Safari when the link preview is tapped. In theory, you shoud be able to get
+ // the link from the elementInfo above and transition to SFSafariViewController instead of launching
+ // Safari. As the time of this writing, the link in elementInfo is always nil. ¯\_(ツ)_/¯
+ }
+}
+
// MARK: Private
private extension ArticleViewController {
From 59143c2d8f8e31ca90927033ac526099ffa25cd9 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Tue, 1 Oct 2019 15:52:26 -0500
Subject: [PATCH 042/189] Comment out FreshRSS on add account
---
.../Account/SettingsAddAccountView.swift | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/iOS/Settings/Account/SettingsAddAccountView.swift b/iOS/Settings/Account/SettingsAddAccountView.swift
index dd47e94d3..f26769971 100644
--- a/iOS/Settings/Account/SettingsAddAccountView.swift
+++ b/iOS/Settings/Account/SettingsAddAccountView.swift
@@ -21,21 +21,22 @@ struct SettingsAddAccountView : View {
}
.modifier(VibrantSelectAction(action: {
self.accountAddAction = 1
- }))
+ })).padding(.vertical, 16)
NavigationLink(destination: SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel()), tag: 2, selection: $accountAddAction) {
SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin")
+
}
.modifier(VibrantSelectAction(action: {
self.accountAddAction = 2
- }))
-
- NavigationLink(destination: SettingsReaderAPIAccountView(viewModel: SettingsReaderAPIAccountView.ViewModel(accountType: .freshRSS)), tag: 3, selection: $accountAddAction) {
- SettingsAccountLabelView(accountImage: "accountFreshRSS", accountLabel: "Fresh RSS")
- }
- .modifier(VibrantSelectAction(action: {
- self.accountAddAction = 3
- }))
+ })).padding(.vertical, 16)
+
+// NavigationLink(destination: SettingsReaderAPIAccountView(viewModel: SettingsReaderAPIAccountView.ViewModel(accountType: .freshRSS)), tag: 3, selection: $accountAddAction) {
+// SettingsAccountLabelView(accountImage: "accountFreshRSS", accountLabel: "Fresh RSS")
+// }
+// .modifier(VibrantSelectAction(action: {
+// self.accountAddAction = 3
+// }))
}
.navigationBarTitle(Text("Add Account"), displayMode: .inline)
From 45ae96218baddce22ec8f3e5a0e1bf275d635831 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Wed, 2 Oct 2019 10:12:22 -0500
Subject: [PATCH 043/189] Set the default appearance for a compact toolbar so
that we don't end up with transparent compact toolbars occasionally
---
iOS/UIKit Extensions/ThemedNavigationController.swift | 2 ++
1 file changed, 2 insertions(+)
diff --git a/iOS/UIKit Extensions/ThemedNavigationController.swift b/iOS/UIKit Extensions/ThemedNavigationController.swift
index 1214f9fe2..979d83b78 100644
--- a/iOS/UIKit Extensions/ThemedNavigationController.swift
+++ b/iOS/UIKit Extensions/ThemedNavigationController.swift
@@ -36,6 +36,7 @@ class ThemedNavigationController: UINavigationController {
navigationBar.standardAppearance = UINavigationBarAppearance()
navigationBar.tintColor = view.tintColor
toolbar.standardAppearance = UIToolbarAppearance()
+ toolbar.compactAppearance = UIToolbarAppearance()
toolbar.tintColor = view.tintColor
} else {
let navigationAppearance = UINavigationBarAppearance()
@@ -48,6 +49,7 @@ class ThemedNavigationController: UINavigationController {
let toolbarAppearance = UIToolbarAppearance()
toolbarAppearance.backgroundColor = UIColor.white
toolbar.standardAppearance = toolbarAppearance
+ toolbar.compactAppearance = toolbarAppearance
toolbar.tintColor = AppAssets.primaryAccentColor
}
From bc32fc1cb913a2ed22dcbb03caa465ada316b673 Mon Sep 17 00:00:00 2001
From: Nate Weaver
Date: Sun, 22 Sep 2019 09:55:19 -0500
Subject: [PATCH 044/189] Add row swipe action for Mark Read/Unread
---
.../Timeline/TimelineViewController.swift | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift
index 8fdb02647..ae8906d12 100644
--- a/Mac/MainWindow/Timeline/TimelineViewController.swift
+++ b/Mac/MainWindow/Timeline/TimelineViewController.swift
@@ -746,6 +746,32 @@ extension TimelineViewController: NSTableViewDelegate {
cell.objectValue = nil
cell.cellData = TimelineCellData()
}
+
+ private func toggleArticleRead(_ article: Article) {
+ guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: [article], markingRead: !article.status.read, undoManager: undoManager) else {
+ return
+ }
+ self.runCommand(markUnreadCommand)
+ }
+
+ func tableView(_ tableView: NSTableView, rowActionsForRow row: Int, edge: NSTableView.RowActionEdge) -> [NSTableViewRowAction] {
+
+ if edge == .leading {
+ guard let article = articles.articleAtRow(row) else {
+ return []
+ }
+
+ let title = article.status.read ? NSLocalizedString("Mark Unread", comment: "mark unread") : NSLocalizedString("Mark Read", comment: "mark read")
+
+ let action = NSTableViewRowAction(style: .regular, title: title) { (action, row) in
+ self.toggleArticleRead(article);
+ tableView.rowActionsVisible = false
+ }
+ return [action]
+ }
+
+ return []
+ }
}
// MARK: - Private
From f67b7df5a9be1903395ddf2fa31162780de36e13 Mon Sep 17 00:00:00 2001
From: Nate Weaver
Date: Tue, 24 Sep 2019 08:50:15 -0500
Subject: [PATCH 045/189] Add row swipe action for Mark/Unmark Starred
---
.../Timeline/TimelineViewController.swift | 38 ++++++++++++++-----
1 file changed, 28 insertions(+), 10 deletions(-)
diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift
index ae8906d12..215a608c2 100644
--- a/Mac/MainWindow/Timeline/TimelineViewController.swift
+++ b/Mac/MainWindow/Timeline/TimelineViewController.swift
@@ -754,20 +754,38 @@ extension TimelineViewController: NSTableViewDelegate {
self.runCommand(markUnreadCommand)
}
+ private func toggleArticleStarred(_ article: Article) {
+ guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: [article], markingStarred: !article.status.starred, undoManager: undoManager) else {
+ return
+ }
+ self.runCommand(markUnreadCommand)
+ }
+
func tableView(_ tableView: NSTableView, rowActionsForRow row: Int, edge: NSTableView.RowActionEdge) -> [NSTableViewRowAction] {
- if edge == .leading {
- guard let article = articles.articleAtRow(row) else {
- return []
- }
+ guard let article = articles.articleAtRow(row) else {
+ return []
+ }
- let title = article.status.read ? NSLocalizedString("Mark Unread", comment: "mark unread") : NSLocalizedString("Mark Read", comment: "mark read")
+ switch edge {
+ case .leading:
+ let title = article.status.read ? NSLocalizedString("Mark Unread", comment: "mark unread") : NSLocalizedString("Mark Read", comment: "mark read")
+ let action = NSTableViewRowAction(style: .regular, title: title) { (action, row) in
+ self.toggleArticleRead(article);
+ tableView.rowActionsVisible = false
+ }
+ return [action]
- let action = NSTableViewRowAction(style: .regular, title: title) { (action, row) in
- self.toggleArticleRead(article);
- tableView.rowActionsVisible = false
- }
- return [action]
+ case .trailing:
+ let title = article.status.starred ? NSLocalizedString("Mark Unstarred", comment: "mark unstarred") : NSLocalizedString("Mark Starred", comment: "mark starred")
+ let action = NSTableViewRowAction(style: .regular, title: title) { (action, row) in
+ self.toggleArticleStarred(article);
+ tableView.rowActionsVisible = false
+ }
+ return [action]
+
+ @unknown default:
+ NSLog("Unknown table row edge: %ld", edge.rawValue)
}
return []
From ef29334a41b92b9bd05eac3ee9cffb810c5d6533 Mon Sep 17 00:00:00 2001
From: Nate Weaver
Date: Wed, 2 Oct 2019 11:18:52 -0500
Subject: [PATCH 046/189] Use os_log() instead of NSLog()
---
Mac/MainWindow/Timeline/TimelineViewController.swift | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift
index 215a608c2..4684058ef 100644
--- a/Mac/MainWindow/Timeline/TimelineViewController.swift
+++ b/Mac/MainWindow/Timeline/TimelineViewController.swift
@@ -10,6 +10,7 @@ import Foundation
import RSCore
import Articles
import Account
+import os.log
protocol TimelineDelegate: class {
func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?)
@@ -785,7 +786,7 @@ extension TimelineViewController: NSTableViewDelegate {
return [action]
@unknown default:
- NSLog("Unknown table row edge: %ld", edge.rawValue)
+ os_log(.error, "Unknown table row edge: %ld", edge.rawValue)
}
return []
From 281416eaee39f689e03dafe814b4e756aeb19a24 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Wed, 2 Oct 2019 15:32:34 -0500
Subject: [PATCH 047/189] Make sure metadata gets saved if background fetch is
performed.
---
Frameworks/Account/Account.swift | 8 ++++----
Frameworks/Account/AccountManager.swift | 4 ++++
Frameworks/Account/AccountMetadataFile.swift | 2 +-
Frameworks/Account/FeedMetadataFile.swift | 2 +-
Frameworks/Account/OPMLFile.swift | 2 +-
iOS/AppDelegate.swift | 3 +++
iOS/ShareExtension/ShareViewController.swift | 2 +-
7 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift
index d0f787da3..6619a627a 100644
--- a/Frameworks/Account/Account.swift
+++ b/Frameworks/Account/Account.swift
@@ -368,10 +368,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
- public func saveIfNecessary() {
- metadataFile.saveIfNecessary()
- feedMetadataFile.saveIfNecessary()
- opmlFile.saveIfNecessary()
+ public func save() {
+ metadataFile.save()
+ feedMetadataFile.save()
+ opmlFile.save()
}
func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) {
diff --git a/Frameworks/Account/AccountManager.swift b/Frameworks/Account/AccountManager.swift
index 21edfcbe5..7db38dc48 100644
--- a/Frameworks/Account/AccountManager.swift
+++ b/Frameworks/Account/AccountManager.swift
@@ -184,6 +184,10 @@ public final class AccountManager: UnreadCountProvider {
}
}
+ public func saveAll() {
+ accounts.forEach { $0.save() }
+ }
+
public func anyAccountHasAtLeastOneFeed() -> Bool {
for account in activeAccounts {
if account.hasAtLeastOneFeed() {
diff --git a/Frameworks/Account/AccountMetadataFile.swift b/Frameworks/Account/AccountMetadataFile.swift
index 03b5ccf7e..0c78f09da 100644
--- a/Frameworks/Account/AccountMetadataFile.swift
+++ b/Frameworks/Account/AccountMetadataFile.swift
@@ -31,7 +31,7 @@ final class AccountMetadataFile {
managedFile.load()
}
- func saveIfNecessary() {
+ func save() {
managedFile.saveIfNecessary()
}
diff --git a/Frameworks/Account/FeedMetadataFile.swift b/Frameworks/Account/FeedMetadataFile.swift
index 42fb8a460..cfc900d83 100644
--- a/Frameworks/Account/FeedMetadataFile.swift
+++ b/Frameworks/Account/FeedMetadataFile.swift
@@ -31,7 +31,7 @@ final class FeedMetadataFile {
managedFile.load()
}
- func saveIfNecessary() {
+ func save() {
managedFile.saveIfNecessary()
}
diff --git a/Frameworks/Account/OPMLFile.swift b/Frameworks/Account/OPMLFile.swift
index 23c4a02c7..d63a6f2a0 100644
--- a/Frameworks/Account/OPMLFile.swift
+++ b/Frameworks/Account/OPMLFile.swift
@@ -32,7 +32,7 @@ final class OPMLFile {
managedFile.load()
}
- func saveIfNecessary() {
+ func save() {
managedFile.saveIfNecessary()
}
diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift
index b75d7b34c..b08a84ce0 100644
--- a/iOS/AppDelegate.swift
+++ b/iOS/AppDelegate.swift
@@ -298,6 +298,9 @@ private extension AppDelegate {
os_log("Account refresh operation completed.", log: self.log, type: .info)
task.setTaskCompleted(success: true)
}
+
+ AccountManager.shared.saveAll()
+
}
// set expiration handler
diff --git a/iOS/ShareExtension/ShareViewController.swift b/iOS/ShareExtension/ShareViewController.swift
index 1510ff04b..f22ba3fb5 100644
--- a/iOS/ShareExtension/ShareViewController.swift
+++ b/iOS/ShareExtension/ShareViewController.swift
@@ -119,7 +119,7 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont
switch result {
case .success:
- account!.saveIfNecessary()
+ account!.save()
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
case .failure(let error):
self.presentError(error) {
From aba0d15cb6173346a4cba68f56395ae18b4dd182 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Wed, 2 Oct 2019 16:41:32 -0500
Subject: [PATCH 048/189] Modify background fetch so that it doesn't have to
use a background process
---
Frameworks/Account/AccountManager.swift | 11 +++-
.../LocalAccount/LocalAccountDelegate.swift | 6 +-
.../LocalAccount/LocalAccountRefresher.swift | 11 +++-
iOS/AppDelegate.swift | 66 ++-----------------
submodules/RSWeb | 2 +-
5 files changed, 30 insertions(+), 66 deletions(-)
diff --git a/Frameworks/Account/AccountManager.swift b/Frameworks/Account/AccountManager.swift
index 7db38dc48..b6eee62d8 100644
--- a/Frameworks/Account/AccountManager.swift
+++ b/Frameworks/Account/AccountManager.swift
@@ -156,9 +156,13 @@ public final class AccountManager: UnreadCountProvider {
return accountsDictionary[accountID]
}
- public func refreshAll(errorHandler: @escaping (Error) -> Void) {
+ public func refreshAll(errorHandler: @escaping (Error) -> Void, completion: (() ->Void)? = nil) {
+ let group = DispatchGroup()
+
activeAccounts.forEach { account in
+ group.enter()
account.refreshAll() { result in
+ group.leave()
switch result {
case .success:
break
@@ -167,6 +171,11 @@ public final class AccountManager: UnreadCountProvider {
}
}
}
+
+ group.notify(queue: DispatchQueue.main) {
+ completion?()
+ }
+
}
public func syncArticleStatusAll(completion: (() -> Void)? = nil) {
diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift
index 5ebd94551..97cc2e2ec 100644
--- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift
+++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift
@@ -31,10 +31,10 @@ final class LocalAccountDelegate: AccountDelegate {
return refresher.progress
}
- // LocalAccountDelegate doesn't wait for completion before calling the completion block
func refreshAll(for account: Account, completion: @escaping (Result) -> Void) {
- refresher.refreshFeeds(account.flattenedFeeds())
- completion(.success(()))
+ refresher.refreshFeeds(account.flattenedFeeds()) {
+ completion(.success(()))
+ }
}
func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) {
diff --git a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift
index 90718a233..de5e08f2c 100644
--- a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift
+++ b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift
@@ -14,6 +14,8 @@ import Articles
final class LocalAccountRefresher {
+ private var completion: (() -> Void)?
+
private lazy var downloadSession: DownloadSession = {
return DownloadSession(delegate: self)
}()
@@ -22,7 +24,8 @@ final class LocalAccountRefresher {
return downloadSession.progress
}
- public func refreshFeeds(_ feeds: Set) {
+ public func refreshFeeds(_ feeds: Set, completion: @escaping () -> Void) {
+ self.completion = completion
downloadSession.downloadObjects(feeds as NSSet)
}
}
@@ -102,6 +105,12 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
func downloadSession(_ downloadSession: DownloadSession, didReceiveNotModifiedResponse: URLResponse, representedObject: AnyObject) {
}
+
+ func downloadSessionDidCompleteDownloadObjects(_ downloadSession: DownloadSession) {
+ completion?()
+ completion = nil
+ }
+
}
// MARK: - Utility
diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift
index b08a84ce0..b3141d7ca 100644
--- a/iOS/AppDelegate.swift
+++ b/iOS/AppDelegate.swift
@@ -265,44 +265,16 @@ private extension AppDelegate {
scheduleBackgroundFeedRefresh() // schedule next refresh
- var startingUnreadCount = 0
-
- DispatchQueue.global(qos: .background).async { [unowned self] in
-
- os_log("Woken to perform account refresh.", log: self.log, type: .info)
-
- os_log("Getting unread count.", log: self.log, type: .info)
- while(!AccountManager.shared.isUnreadCountsInitialized) {
- os_log("Waiting for unread counts to be initialized...", log: self.log, type: .info)
- sleep(1)
- }
- os_log(.info, log: self.log, "Got unread count: %i", self.unreadCount)
- startingUnreadCount = self.unreadCount
-
- DispatchQueue.main.async {
- AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
- }
- os_log("Accounts requested to begin refresh.", log: self.log, type: .info)
-
- sleep(1)
- while (!AccountManager.shared.combinedRefreshProgress.isComplete) {
- os_log("Waiting for account refresh processing to complete...", log: self.log, type: .info)
- sleep(1)
- }
-
- if startingUnreadCount < self.unreadCount {
- os_log("Updating unread count badge, posting notification.", log: self.log, type: .info)
- self.sendReceivedArticlesUserNotification(newArticleCount: self.unreadCount - startingUnreadCount)
- task.setTaskCompleted(success: true)
- } else {
+ os_log("Woken to perform account refresh.", log: self.log, type: .info)
+
+ DispatchQueue.main.async {
+ AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) {
+ AccountManager.shared.saveAll()
os_log("Account refresh operation completed.", log: self.log, type: .info)
task.setTaskCompleted(success: true)
}
-
- AccountManager.shared.saveAll()
-
}
-
+
// set expiration handler
task.expirationHandler = {
os_log("Accounts refresh processing terminated for running too long.", log: self.log, type: .info)
@@ -311,29 +283,3 @@ private extension AppDelegate {
}
}
-
-private extension AppDelegate {
-
- func sendReceivedArticlesUserNotification(newArticleCount: Int) {
-
- let content = UNMutableNotificationContent()
- content.title = NSLocalizedString("Article Download", comment: "New Articles")
-
- let body: String = {
- if newArticleCount == 1 {
- return NSLocalizedString("You have downloaded 1 new article.", comment: "Article Downloaded")
- } else {
- let formatString = NSLocalizedString("You have downloaded %d new articles.", comment: "Articles Downloaded")
- return NSString.localizedStringWithFormat(formatString as NSString, newArticleCount) as String
- }
- }()
-
- content.body = body
- content.sound = UNNotificationSound.default
-
- let request = UNNotificationRequest.init(identifier: "NewArticlesReceived", content: content, trigger: nil)
- UNUserNotificationCenter.current().add(request)
-
- }
-
-}
diff --git a/submodules/RSWeb b/submodules/RSWeb
index 9cb7ca961..9d5a76e50 160000
--- a/submodules/RSWeb
+++ b/submodules/RSWeb
@@ -1 +1 @@
-Subproject commit 9cb7ca96182b3320882522708a2b4dcdaafb07f6
+Subproject commit 9d5a76e50d74643b331169dbd10b170c585ca979
From cc187875d9c28ab75cf7de98e901a0e9439e0334 Mon Sep 17 00:00:00 2001
From: Maurice Parker
Date: Wed, 2 Oct 2019 19:42:16 -0500
Subject: [PATCH 049/189] Add initial support for per feed notifications
---
Frameworks/Account/Feed.swift | 9 ++++
Frameworks/Account/FeedMetadata.swift | 11 ++++-
Mac/AppDelegate.swift | 23 ++++++++-
.../FeedInspectorViewController.swift | 18 +++++--
Mac/Inspector/Inspector.storyboard | 31 ++++++++----
NetNewsWire.xcodeproj/project.pbxproj | 14 ++++++
.../UserNotificationManager.swift | 48 +++++++++++++++++++
iOS/AppDelegate.swift | 13 +++--
iOS/Inspector/FeedInspectorView.swift | 13 +++++
9 files changed, 161 insertions(+), 19 deletions(-)
create mode 100644 Shared/UserNotifications/UserNotificationManager.swift
diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/Feed.swift
index 3ed63b50f..c2855ef30 100644
--- a/Frameworks/Account/Feed.swift
+++ b/Frameworks/Account/Feed.swift
@@ -123,6 +123,15 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
}
}
+ public var isNotifyAboutNewArticles: Bool? {
+ get {
+ return metadata.isNotifyAboutNewArticles
+ }
+ set {
+ metadata.isNotifyAboutNewArticles = newValue
+ }
+ }
+
public var isArticleExtractorAlwaysOn: Bool? {
get {
return metadata.isArticleExtractorAlwaysOn
diff --git a/Frameworks/Account/FeedMetadata.swift b/Frameworks/Account/FeedMetadata.swift
index 7da7ac9c0..a85c259f2 100644
--- a/Frameworks/Account/FeedMetadata.swift
+++ b/Frameworks/Account/FeedMetadata.swift
@@ -24,6 +24,7 @@ final class FeedMetadata: Codable {
case editedName
case authors
case contentHash
+ case isNotifyAboutNewArticles
case isArticleExtractorAlwaysOn
case conditionalGetInfo
case subscriptionID
@@ -78,10 +79,18 @@ final class FeedMetadata: Codable {
}
}
+ var isNotifyAboutNewArticles: Bool? {
+ didSet {
+ if isNotifyAboutNewArticles != oldValue {
+ valueDidChange(.isNotifyAboutNewArticles)
+ }
+ }
+ }
+
var isArticleExtractorAlwaysOn: Bool? {
didSet {
if isArticleExtractorAlwaysOn != oldValue {
- valueDidChange(.contentHash)
+ valueDidChange(.isArticleExtractorAlwaysOn)
}
}
}
diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift
index 9a56b3b1e..afb8c2ca3 100644
--- a/Mac/AppDelegate.swift
+++ b/Mac/AppDelegate.swift
@@ -7,6 +7,7 @@
//
import AppKit
+import UserNotifications
import Articles
import RSTree
import RSWeb
@@ -16,8 +17,9 @@ import RSCore
var appDelegate: AppDelegate!
@NSApplicationMain
-class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UnreadCountProvider {
+class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UNUserNotificationCenterDelegate, UnreadCountProvider {
+ var userNotificationManager: UserNotificationManager!
var faviconDownloader: FaviconDownloader!
var imageDownloader: ImageDownloader!
var authorAvatarDownloader: AuthorAvatarDownloader!
@@ -130,7 +132,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
}
let localAccount = AccountManager.shared.defaultAccount
DefaultFeedsImporter.importIfNeeded(isFirstRun, account: localAccount)
-
+
let tempDirectory = NSTemporaryDirectory()
let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String)
let cacheFolder = (tempDirectory as NSString).appendingPathComponent(bundleIdentifier)
@@ -179,6 +181,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
refreshTimer = AccountRefreshTimer()
syncTimer = ArticleStatusSyncTimer()
+ UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .sound, .alert]) { (granted, error) in
+ if granted {
+ DispatchQueue.main.async {
+ NSApplication.shared.registerForRemoteNotifications()
+ }
+ }
+ }
+
+ UNUserNotificationCenter.current().delegate = self
+ userNotificationManager = UserNotificationManager()
+
#if RELEASE
debugMenuItem.menu?.removeItem(debugMenuItem)
DispatchQueue.main.async {
@@ -322,6 +335,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
return true
}
+ // MARK: UNUserNotificationCenterDelegate
+
+ func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+ completionHandler([.alert, .badge, .sound])
+ }
+
// MARK: Add Feed
func addFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) {
diff --git a/Mac/Inspector/FeedInspectorViewController.swift b/Mac/Inspector/FeedInspectorViewController.swift
index 42caa1418..0a3cef52d 100644
--- a/Mac/Inspector/FeedInspectorViewController.swift
+++ b/Mac/Inspector/FeedInspectorViewController.swift
@@ -12,10 +12,11 @@ import Account
final class FeedInspectorViewController: NSViewController, Inspector {
- @IBOutlet var imageView: NSImageView?
- @IBOutlet var nameTextField: NSTextField?
- @IBOutlet var homePageURLTextField: NSTextField?
- @IBOutlet var urlTextField: NSTextField?
+ @IBOutlet weak var imageView: NSImageView?
+ @IBOutlet weak var nameTextField: NSTextField?
+ @IBOutlet weak var homePageURLTextField: NSTextField?
+ @IBOutlet weak var urlTextField: NSTextField?
+ @IBOutlet weak var isNotifyAboutNewArticlesCheckBox: NSButton!
@IBOutlet weak var isReaderViewAlwaysOnCheckBox: NSButton?
private var feed: Feed? {
@@ -51,6 +52,10 @@ final class FeedInspectorViewController: NSViewController, Inspector {
}
// MARK: Actions
+ @IBAction func isNotifyAboutNewArticlesChanged(_ sender: Any) {
+ feed?.isNotifyAboutNewArticles = (isNotifyAboutNewArticlesCheckBox?.state ?? .off) == .on ? true : false
+ }
+
@IBAction func isReaderViewAlwaysOnChanged(_ sender: Any) {
feed?.isArticleExtractorAlwaysOn = (isReaderViewAlwaysOnCheckBox?.state ?? .off) == .on ? true : false
}
@@ -89,6 +94,7 @@ private extension FeedInspectorViewController {
updateName()
updateHomePageURL()
updateFeedURL()
+ updateNotifyAboutNewArticles()
updateIsReaderViewAlwaysOn()
view.needsLayout = true
@@ -135,6 +141,10 @@ private extension FeedInspectorViewController {
urlTextField?.stringValue = feed?.url ?? ""
}
+ func updateNotifyAboutNewArticles() {
+ isNotifyAboutNewArticlesCheckBox?.state = (feed?.isNotifyAboutNewArticles ?? false) ? .on : .off
+ }
+
func updateIsReaderViewAlwaysOn() {
isReaderViewAlwaysOnCheckBox?.state = (feed?.isArticleExtractorAlwaysOn ?? false) ? .on : .off
}
diff --git a/Mac/Inspector/Inspector.storyboard b/Mac/Inspector/Inspector.storyboard
index 4f5f22bf5..eb460de87 100644
--- a/Mac/Inspector/Inspector.storyboard
+++ b/Mac/Inspector/Inspector.storyboard
@@ -1,8 +1,8 @@
-
+
-
+
@@ -34,11 +34,11 @@
-
+
-
+
@@ -46,7 +46,7 @@
-
+
@@ -104,13 +104,24 @@ Field
+
+
+
+
+
+
+
+
+
+
-
+
+
@@ -120,6 +131,7 @@ Field
+
@@ -131,6 +143,7 @@ Field
+
@@ -138,7 +151,7 @@ Field
-
+
@@ -189,7 +202,7 @@ Field
-
+
@@ -231,7 +244,7 @@ Field
-
+
diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj
index 9b35dae91..21a1cdebc 100644
--- a/NetNewsWire.xcodeproj/project.pbxproj
+++ b/NetNewsWire.xcodeproj/project.pbxproj
@@ -218,6 +218,8 @@
51FA73B72332D5F70090D516 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */; };
51FD40C72341555A00880194 /* UIImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD40BD2341555600880194 /* UIImage-Extensions.swift */; };
51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */; };
+ 51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; };
+ 51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; };
55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */; };
55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; };
5F323809231DF9F000706F6B /* NNWTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F323808231DF9F000706F6B /* NNWTableViewCell.swift */; };
@@ -899,6 +901,7 @@
51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; };
51FD40BD2341555600880194 /* UIImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage-Extensions.swift"; sourceTree = ""; };
51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineUnreadCountView.swift; sourceTree = ""; };
+ 51FE10022345529D0056195D /* UserNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationManager.swift; sourceTree = ""; };
557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsReaderAPIAccountView.swift; sourceTree = ""; };
55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsReaderAPI.xib; sourceTree = ""; };
55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsReaderAPIWindowController.swift; sourceTree = ""; };
@@ -1456,6 +1459,14 @@
path = "Article Extractor";
sourceTree = "";
};
+ 51FE0FF9234552490056195D /* UserNotifications */ = {
+ isa = PBXGroup;
+ children = (
+ 51FE10022345529D0056195D /* UserNotificationManager.swift */,
+ );
+ path = UserNotifications;
+ sourceTree = "";
+ };
6581C73620CED60100F4AD34 /* SafariExtension */ = {
isa = PBXGroup;
children = (
@@ -1879,6 +1890,7 @@
849A97861ED9ECEF007D329B /* Article Styles */,
84DAEE201F86CAE00058304B /* Importers */,
8444C9011FED81880051386C /* Exporters */,
+ 51FE0FF9234552490056195D /* UserNotifications */,
84F2D5341FC22FCB00998D64 /* SmartFeeds */,
848F6AE31FC29CFA002D422E /* Favicons */,
845213211FCA5B10003B6E93 /* Images */,
@@ -2830,6 +2842,7 @@
51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */,
5F323809231DF9F000706F6B /* NNWTableViewCell.swift in Sources */,
512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */,
+ 51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */,
51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */,
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */,
51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */,
@@ -2953,6 +2966,7 @@
5144EA43227A380F00D19003 /* ExportOPMLWindowController.swift in Sources */,
842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */,
84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */,
+ 51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */,
D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */,
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */,
diff --git a/Shared/UserNotifications/UserNotificationManager.swift b/Shared/UserNotifications/UserNotificationManager.swift
new file mode 100644
index 000000000..842b39778
--- /dev/null
+++ b/Shared/UserNotifications/UserNotificationManager.swift
@@ -0,0 +1,48 @@
+//
+// NotificationManager.swift
+// NetNewsWire
+//
+// Created by Maurice Parker on 10/2/19.
+// Copyright © 2019 Ranchero Software. All rights reserved.
+//
+
+import Foundation
+import Account
+import Articles
+import UserNotifications
+
+final class UserNotificationManager: NSObject {
+
+ override init() {
+ super.init()
+ NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
+ }
+
+ @objc func accountDidDownloadArticles(_ note: Notification) {
+ guard let articles = note.userInfo?[Account.UserInfoKey.newArticles] as? Set else {
+ return
+ }
+
+ for article in articles {
+ if let feed = article.feed, feed.isNotifyAboutNewArticles ?? false {
+ sendNotification(feed: feed, article: article)
+ }
+ }
+ }
+
+}
+
+private extension UserNotificationManager {
+
+ private func sendNotification(feed: Feed, article: Article) {
+ let content = UNMutableNotificationContent()
+
+ content.title = feed.nameForDisplay
+ content.body = article.title ?? article.summary ?? ""
+ content.sound = UNNotificationSound.default
+
+ let request = UNNotificationRequest.init(identifier: "articleID:\(article.articleID)", content: content, trigger: nil)
+ UNUserNotificationCenter.current().add(request)
+ }
+
+}
diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift
index b3141d7ca..ab37ce762 100644
--- a/iOS/AppDelegate.swift
+++ b/iOS/AppDelegate.swift
@@ -10,14 +10,13 @@ import UIKit
import RSCore
import RSWeb
import Account
-import UserNotifications
import BackgroundTasks
import os.log
var appDelegate: AppDelegate!
@UIApplicationMain
-class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate, UnreadCountProvider {
+class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider {
private var syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
@@ -34,6 +33,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "application")
+ var userNotificationManager: UserNotificationManager!
var faviconDownloader: FaviconDownloader!
var imageDownloader: ImageDownloader!
var authorAvatarDownloader: AuthorAvatarDownloader!
@@ -90,7 +90,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
}
}
}
-
+
+ UNUserNotificationCenter.current().delegate = self
+ userNotificationManager = UserNotificationManager()
+
syncTimer = ArticleStatusSyncTimer()
#if DEBUG
@@ -171,6 +174,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
logMessage(message, type: .debug)
}
+ func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+ completionHandler([.alert, .badge, .sound])
+ }
+
}
// MARK: App Initialization
diff --git a/iOS/Inspector/FeedInspectorView.swift b/iOS/Inspector/FeedInspectorView.swift
index 95bc6b080..19edf5b23 100644
--- a/iOS/Inspector/FeedInspectorView.swift
+++ b/iOS/Inspector/FeedInspectorView.swift
@@ -52,6 +52,9 @@ struct FeedInspectorView : View {
Spacer()
}) {
TextField("Feed Name", text: $viewModel.name)
+ Toggle(isOn: $viewModel.isNotifyAboutNewArticles) {
+ Text("Notify About New Articles")
+ }
Toggle(isOn: $viewModel.isArticleExtractorAlwaysOn) {
Text("Always Show Reader View")
}
@@ -108,6 +111,16 @@ struct FeedInspectorView : View {
}
}
+ var isNotifyAboutNewArticles: Bool {
+ get {
+ return feed.isNotifyAboutNewArticles ?? false
+ }
+ set {
+ objectWillChange.send()
+ feed.isNotifyAboutNewArticles = newValue
+ }
+ }
+
var isArticleExtractorAlwaysOn: Bool {
get {
return feed.isArticleExtractorAlwaysOn ?? false
From e3c50db7e8a12909e7d67b320e85fec97e368e77 Mon Sep 17 00:00:00 2001
From: Ramy Majouji
Date: Wed, 2 Oct 2019 22:52:50 -0400
Subject: [PATCH 050/189] =?UTF-8?q?Optimize=20PNG=E2=80=99s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../AppIcon.appiconset/icon_128x128.png | Bin 22415 -> 20872 bytes
.../AppIcon.appiconset/icon_128x128@2x.png | Bin 68106 -> 61566 bytes
.../AppIcon.appiconset/icon_16x16.png | Bin 1067 -> 1003 bytes
.../AppIcon.appiconset/icon_16x16@2x.png | Bin 2813 -> 2714 bytes
.../AppIcon.appiconset/icon_256x256.png | Bin 68106 -> 61566 bytes
.../AppIcon.appiconset/icon_256x256@2x.png | Bin 212829 -> 188013 bytes
.../AppIcon.appiconset/icon_32x32.png | Bin 2813 -> 2714 bytes
.../AppIcon.appiconset/icon_32x32@2x.png | Bin 7895 -> 7276 bytes
.../AppIcon.appiconset/icon_512x512.png | Bin 212829 -> 188013 bytes
.../AppIcon.appiconset/icon_512x512@2x.png | Bin 654491 -> 577231 bytes
.../action.imageset/action.png | Bin 524 -> 406 bytes
.../action.imageset/action@2x.png | Bin 939 -> 704 bytes
.../markAllRead.imageset/markAllRead.png | Bin 1262 -> 1120 bytes
.../markAllRead.imageset/markAllRead@2x.png | Bin 2187 -> 1817 bytes
.../markRead.imageset/markRead.png | Bin 870 -> 777 bytes
.../markRead.imageset/markRead@2x.png | Bin 1611 -> 1445 bytes
.../markUnread.imageset/markUnread.png | Bin 551 -> 495 bytes
.../markUnread.imageset/markUnread@2x.png | Bin 847 -> 782 bytes
.../newFolder.imageset/newFolder.png | Bin 689 -> 595 bytes
.../newFolder.imageset/newFolder@2x.png | Bin 1328 -> 1148 bytes
.../nextUnread.imageset/nextUnread.png | Bin 14945 -> 14851 bytes
.../nextUnread.imageset/nextUnread@2x.png | Bin 15896 -> 15589 bytes
.../openInBrowser.imageset/openInBrowser.png | Bin 888 -> 791 bytes
.../openInBrowser@2x.png | Bin 1703 -> 1577 bytes
.../Assets.xcassets/star.imageset/star.png | Bin 516 -> 462 bytes
.../Assets.xcassets/star.imageset/star@2x.png | Bin 742 -> 678 bytes
.../timelineStar.imageset/timelineStar.png | Bin 981 -> 875 bytes
.../timelineStar.imageset/timelineStar@2x.png | Bin 1303 -> 1048 bytes
.../unstar.imageset/unstar.png | Bin 389 -> 298 bytes
.../unstar.imageset/unstar@2x.png | Bin 682 -> 505 bytes
Technotes/Images/Branching-Full.png | Bin 40915 -> 29164 bytes
Technotes/Images/Branching.png | Bin 21802 -> 15036 bytes
Technotes/Images/icon.png | Bin 2177 -> 1992 bytes
.../AppIcon.appiconset/icon-1024.png | Bin 793531 -> 705697 bytes
.../AppIcon.appiconset/icon-120.png | Bin 12600 -> 11137 bytes
.../AppIcon.appiconset/icon-121.png | Bin 12600 -> 11137 bytes
.../AppIcon.appiconset/icon-152.png | Bin 18740 -> 16440 bytes
.../AppIcon.appiconset/icon-167.png | Bin 22001 -> 19272 bytes
.../AppIcon.appiconset/icon-180.png | Bin 25172 -> 21820 bytes
.../AppIcon.appiconset/icon-20.png | Bin 15387 -> 15258 bytes
.../AppIcon.appiconset/icon-29.png | Bin 1659 -> 1469 bytes
.../AppIcon.appiconset/icon-40.png | Bin 2522 -> 2316 bytes
.../AppIcon.appiconset/icon-41.png | Bin 2522 -> 2316 bytes
.../AppIcon.appiconset/icon-42.png | Bin 2522 -> 2316 bytes
.../AppIcon.appiconset/icon-58.png | Bin 4258 -> 3886 bytes
.../AppIcon.appiconset/icon-59.png | Bin 4258 -> 3886 bytes
.../AppIcon.appiconset/icon-60.png | Bin 4514 -> 4061 bytes
.../AppIcon.appiconset/icon-76.png | Bin 6251 -> 5693 bytes
.../AppIcon.appiconset/icon-80.png | Bin 6720 -> 6115 bytes
.../AppIcon.appiconset/icon-81.png | Bin 6720 -> 6115 bytes
.../AppIcon.appiconset/icon-87.png | Bin 7685 -> 6931 bytes
51 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/Mac/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/Mac/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
index db26cded968124c20c0706e49061e5e4809db889..9b8e86460e30621d84618240c2785788b0ee79df 100644
GIT binary patch
delta 20665
zcmV)BK*PU}uK|do0kC=je^2;HL_t(|0qmLua3o2RwZBLz60=&(OoIzE%gi*k%*@Qp
z%+xoq%*>2O!+gWdOyVl3Fe&1nD*wx>EKPkoecQ`&Gq*@qD&Ow5?A_4w}@16iV
zF`hyNFwOCAM~FoNs5?43kNjeXxACFtGo78Qy{xy&Qfhe=+oOT6`jxG^P%5i6sZ