From 7824939c3084d16216b4e341be08603d5120b5a4 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 6 Jan 2020 13:58:51 -0700 Subject: [PATCH 01/11] Make OPML generate in a consistent order when the Web Feed name is the same. Issue #1545 --- Frameworks/Account/Account.swift | 4 ++-- Frameworks/Account/Folder.swift | 13 ++++++++++++- Frameworks/Account/WebFeed.swift | 10 ++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 4eac70446..99011abf2 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -1245,10 +1245,10 @@ extension Account: OPMLRepresentable { public func OPMLString(indentLevel: Int, strictConformance: Bool) -> String { var s = "" - for feed in topLevelWebFeeds.sorted(by: { $0.nameForDisplay < $1.nameForDisplay }) { + for feed in topLevelWebFeeds.sorted() { s += feed.OPMLString(indentLevel: indentLevel + 1, strictConformance: strictConformance) } - for folder in folders!.sorted(by: { $0.nameForDisplay < $1.nameForDisplay }) { + for folder in folders!.sorted() { s += folder.OPMLString(indentLevel: indentLevel + 1, strictConformance: strictConformance) } return s diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index 31bb6e4e3..e69e4c939 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -189,7 +189,7 @@ extension Folder: OPMLRepresentable { var hasAtLeastOneChild = false - for feed in topLevelWebFeeds.sorted(by: { $0.nameForDisplay < $1.nameForDisplay }) { + for feed in topLevelWebFeeds.sorted() { s += feed.OPMLString(indentLevel: indentLevel + 1, strictConformance: strictConformance) hasAtLeastOneChild = true } @@ -206,3 +206,14 @@ extension Folder: OPMLRepresentable { } } +// MARK: Set + +extension Set where Element == Folder { + + func sorted() -> Array { + return sorted(by: { (folder1, folder2) -> Bool in + return folder1.nameForDisplay.localizedStandardCompare(folder2.nameForDisplay) == .orderedAscending + }) + } + +} diff --git a/Frameworks/Account/WebFeed.swift b/Frameworks/Account/WebFeed.swift index 8fd7d0e7c..c01605c74 100644 --- a/Frameworks/Account/WebFeed.swift +++ b/Frameworks/Account/WebFeed.swift @@ -276,4 +276,14 @@ extension Set where Element == WebFeed { func webFeedIDs() -> Set { return Set(map { $0.webFeedID }) } + + func sorted() -> Array { + return sorted(by: { (webFeed1, webFeed2) -> Bool in + if webFeed1.nameForDisplay.localizedStandardCompare(webFeed2.nameForDisplay) == .orderedSame { + return webFeed1.url < webFeed2.url + } + return webFeed1.nameForDisplay.localizedStandardCompare(webFeed2.nameForDisplay) == .orderedAscending + }) + } + } From c0f76d0dbbf3d52571da8dd451a571d70d43de85 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 6 Jan 2020 18:07:04 -0700 Subject: [PATCH 02/11] Change Mark All as Read icon. Issue #1526 --- iOS/AppAssets.swift | 4 +- iOS/Base.lproj/Main.storyboard | 4 +- iOS/MasterFeed/MasterFeedViewController.swift | 2 +- .../MasterTimelineViewController.swift | 2 +- .../markAllAsRead.symbolset/Contents.json | 12 ++ .../markAllAsRead.symbolset/markAllAsRead.svg | 142 ++++++++++++++++++ 6 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 iOS/Resources/Assets.xcassets/markAllAsRead.symbolset/Contents.json create mode 100644 iOS/Resources/Assets.xcassets/markAllAsRead.symbolset/markAllAsRead.svg diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 5d7749b09..6a97cef28 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -113,8 +113,8 @@ struct AppAssets { UIImage(systemName: "info.circle")! }() - static var markAllInFeedAsReadImage: UIImage = { - return UIImage(systemName: "asterisk.circle")! + static var markAllAsReadImage: UIImage = { + return UIImage(named: "markAllAsRead")! }() static var markBelowAsReadImage: UIImage = { diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard index e579a5ab0..e49f78bd6 100644 --- a/iOS/Base.lproj/Main.storyboard +++ b/iOS/Base.lproj/Main.storyboard @@ -119,7 +119,7 @@ - + @@ -367,13 +367,13 @@ - + diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 33702af2a..9987eef5d 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -1101,7 +1101,7 @@ private extension MasterFeedViewController { let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command") let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, nameForDisplay) as String - let action = UIAction(title: title, image: AppAssets.markAllInFeedAsReadImage) { [weak self] action in + let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in self?.coordinator.markAllAsRead(articles) } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index f6584928c..45d327c07 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -767,7 +767,7 @@ private extension MasterTimelineViewController { let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command") let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String - let action = UIAction(title: title, image: AppAssets.markAllInFeedAsReadImage) { [weak self] action in + let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in self?.coordinator.markAllAsRead(articles) } return action diff --git a/iOS/Resources/Assets.xcassets/markAllAsRead.symbolset/Contents.json b/iOS/Resources/Assets.xcassets/markAllAsRead.symbolset/Contents.json new file mode 100644 index 000000000..a83100300 --- /dev/null +++ b/iOS/Resources/Assets.xcassets/markAllAsRead.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "symbols" : [ + { + "idiom" : "universal", + "filename" : "markAllAsRead.svg" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iOS/Resources/Assets.xcassets/markAllAsRead.symbolset/markAllAsRead.svg b/iOS/Resources/Assets.xcassets/markAllAsRead.symbolset/markAllAsRead.svg new file mode 100644 index 000000000..58170a6ba --- /dev/null +++ b/iOS/Resources/Assets.xcassets/markAllAsRead.symbolset/markAllAsRead.svg @@ -0,0 +1,142 @@ + + + + Untitled + Created with Sketch. + + + + + + + Weight/Scale Variations + + + Ultralight + + + Thin + + + Light + + + Regular + + + Medium + + + Semibold + + + Bold + + + Heavy + + + Black + + + + + + + + + + + + + Design Variations + + + Symbols are supported in up to nine weights and three scales. + + + For optimal layout with text and other symbols, vertically align + + + symbols with the adjacent text. + + + + + + + + Margins + + + Leading and trailing margins on the left and right side of each symbol + + + can be adjusted by modifying the width of the blue rectangles. + + + Modifications are automatically applied proportionally to all + + + scales and weights. + + + + + + Exporting + + + Symbols should be outlined when exporting to ensure the + + + design is preserved when submitting to Xcode. + + + Template v.1.0 + + + Generated from circle + + + Typeset at 100 points + + + Small + + + Medium + + + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 9550b29a01aa4424009a0a114a8587270a4f0484 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 6 Jan 2020 18:32:17 -0700 Subject: [PATCH 03/11] Move filter button to bottom left. Issues #1525 and #1527 --- iOS/Base.lproj/LaunchScreenPad.storyboard | 2 +- iOS/Base.lproj/LaunchScreenPhone.storyboard | 2 +- iOS/Base.lproj/Main.storyboard | 23 ++++++++----------- iOS/MasterFeed/MasterFeedViewController.swift | 15 +++++++----- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/iOS/Base.lproj/LaunchScreenPad.storyboard b/iOS/Base.lproj/LaunchScreenPad.storyboard index edb676e06..da3b37910 100644 --- a/iOS/Base.lproj/LaunchScreenPad.storyboard +++ b/iOS/Base.lproj/LaunchScreenPad.storyboard @@ -65,12 +65,12 @@ + - diff --git a/iOS/Base.lproj/LaunchScreenPhone.storyboard b/iOS/Base.lproj/LaunchScreenPhone.storyboard index 6ac462068..e2fb7fa5c 100644 --- a/iOS/Base.lproj/LaunchScreenPhone.storyboard +++ b/iOS/Base.lproj/LaunchScreenPhone.storyboard @@ -65,12 +65,12 @@ + - diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard index e49f78bd6..5f91ef60a 100644 --- a/iOS/Base.lproj/Main.storyboard +++ b/iOS/Base.lproj/Main.storyboard @@ -119,12 +119,12 @@ - + - + - + @@ -139,9 +139,12 @@ - + + + + - + @@ -192,17 +195,9 @@ - - - - - - - - @@ -373,7 +368,7 @@ - + diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 9987eef5d..ad6fd9c35 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -14,7 +14,7 @@ import RSTree class MasterFeedViewController: UITableViewController, UndoableCommandRunner { - @IBOutlet weak var filterButton: UIBarButtonItem! + private var filterButton: UIBarButtonItem! private var refreshProgressView: RefreshProgressView? private var addNewItemButton: UIBarButtonItem! @@ -400,7 +400,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { coordinator.showSettings() } - @IBAction func toggleFilter(_ sender: Any) { + @objc func toggleFilter(_ sender: Any) { if coordinator.isReadFeedsFiltered { filterButton.image = AppAssets.filterInactiveImage coordinator.showAllFeeds() @@ -410,7 +410,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } } - @IBAction func add(_ sender: UIBarButtonItem) { + @objc func add(_ sender: UIBarButtonItem) { coordinator.showAdd(.feed) } @@ -669,12 +669,15 @@ private extension MasterFeedViewController { self.refreshProgressView = refreshProgressView + filterButton = UIBarButtonItem(image: AppAssets.filterInactiveImage, style: .plain, target: self, action: #selector(toggleFilter(_:))) + filterButton.accLabelText = NSLocalizedString("Filter Feeds", comment: "Filter Feeds") let spaceItemButton1 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) let refreshProgressItemButton = UIBarButtonItem(customView: refreshProgressView) let spaceItemButton2 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) addNewItemButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add(_:))) - setToolbarItems([spaceItemButton1, + setToolbarItems([filterButton, + spaceItemButton1, refreshProgressItemButton, spaceItemButton2, addNewItemButton @@ -683,9 +686,9 @@ private extension MasterFeedViewController { func updateUI() { if coordinator.isReadFeedsFiltered { - filterButton.image = AppAssets.filterActiveImage + filterButton?.image = AppAssets.filterActiveImage } else { - filterButton.image = AppAssets.filterInactiveImage + filterButton?.image = AppAssets.filterInactiveImage } refreshProgressView?.updateRefreshLabel() addNewItemButton?.isEnabled = !AccountManager.shared.activeAccounts.isEmpty From bd7449cc8462c49ce4f4bbfe02c867e9a90e8c97 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 6 Jan 2020 18:43:23 -0700 Subject: [PATCH 04/11] Change refresh indicator to never use seconds. Issue #1524 --- iOS/MasterFeed/RefreshProgressView.swift | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/iOS/MasterFeed/RefreshProgressView.swift b/iOS/MasterFeed/RefreshProgressView.swift index f3b22cd3e..69e5c8492 100644 --- a/iOS/MasterFeed/RefreshProgressView.swift +++ b/iOS/MasterFeed/RefreshProgressView.swift @@ -14,7 +14,6 @@ class RefreshProgressView: UIView { @IBOutlet weak var progressView: UIProgressView! @IBOutlet weak var label: UILabel! private lazy var progressWidth = progressView.widthAnchor.constraint(equalToConstant: 100.0) - private var lastLabelDisplayedTime: Date? = nil override func awakeFromNib() { NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) @@ -29,13 +28,7 @@ class RefreshProgressView: UIView { func updateRefreshLabel() { if let accountLastArticleFetchEndTime = AccountManager.shared.lastArticleFetchEndTime { - if let lastLabelDisplayedTime = lastLabelDisplayedTime, lastLabelDisplayedTime.addingTimeInterval(2) > Date() { - return - } - - lastLabelDisplayedTime = Date() - - if Date() > accountLastArticleFetchEndTime.addingTimeInterval(1) { + if Date() > accountLastArticleFetchEndTime.addingTimeInterval(60) { let relativeDateTimeFormatter = RelativeDateTimeFormatter() relativeDateTimeFormatter.dateTimeStyle = .named @@ -45,7 +38,7 @@ class RefreshProgressView: UIView { label.text = refreshText } else { - label.text = NSLocalizedString("Updated just now", comment: "Updated Just Now") + label.text = NSLocalizedString("Updated Just Now", comment: "Updated Just Now") } } else { @@ -80,7 +73,6 @@ private extension RefreshProgressView { self.progressWidth.isActive = false } } else { - lastLabelDisplayedTime = nil label.isHidden = true progressView.isHidden = false self.progressWidth.isActive = true From 9ba0b37cf958cb7b05cf64148928488e517bb80a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 6 Jan 2020 19:09:46 -0700 Subject: [PATCH 05/11] Make progress bar less jumpy. Issue #14 --- iOS/MasterFeed/RefreshProgressView.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/iOS/MasterFeed/RefreshProgressView.swift b/iOS/MasterFeed/RefreshProgressView.swift index 69e5c8492..b793d276d 100644 --- a/iOS/MasterFeed/RefreshProgressView.swift +++ b/iOS/MasterFeed/RefreshProgressView.swift @@ -65,19 +65,26 @@ private extension RefreshProgressView { let progress = AccountManager.shared.combinedRefreshProgress if progress.isComplete { - progressView.progress = 1 + progressView.setProgress(1, animated: true) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.updateRefreshLabel() self.label.isHidden = false self.progressView.isHidden = true self.progressWidth.isActive = false + self.progressView.setProgress(0, animated: true) } } else { label.isHidden = true progressView.isHidden = false self.progressWidth.isActive = true + self.progressView.setNeedsLayout() + self.progressView.layoutIfNeeded() let percent = Float(progress.numberCompleted) / Float(progress.numberOfTasks) - progressView.progress = percent + + // Don't let the progress bar go backwards unless we need to go back more than 25% + if percent > progressView.progress || progressView.progress - percent > 0.25 { + progressView.setProgress(percent, animated: true) + } } } } From c583c4d474d7770c12422756a56ccfe5c41aba8a Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 6 Jan 2020 18:18:27 -0800 Subject: [PATCH 06/11] Fetch both read and unread orphaned statuses. --- Frameworks/ArticlesDatabase/StatusesTable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Frameworks/ArticlesDatabase/StatusesTable.swift b/Frameworks/ArticlesDatabase/StatusesTable.swift index a9e67d5eb..04916d3de 100644 --- a/Frameworks/ArticlesDatabase/StatusesTable.swift +++ b/Frameworks/ArticlesDatabase/StatusesTable.swift @@ -108,7 +108,7 @@ final class StatusesTable: DatabaseTable { var articleIDs = Set() func makeDatabaseCall(_ database: FMDatabase) { - let sql = "select articleID from statuses s where ((starred=1) || (read=0 and dateArrived > ?)) and userDeleted=0 and not exists (select 1 from articles a where a.articleID = s.articleID);" + let sql = "select articleID from statuses s where (starred=1 or dateArrived>?) and userDeleted=0 and not exists (select 1 from articles a where a.articleID = s.articleID);" if let resultSet = database.executeQuery(sql, withArgumentsIn: [cutoffDate]) { articleIDs = resultSet.mapToSet(self.articleIDWithRow) } From 7a7557ac39c86337ff4393c83ffa618533af1723 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 6 Jan 2020 18:23:46 -0800 Subject: [PATCH 07/11] =?UTF-8?q?Delete=20empty=20line.=20(Trying=20to=20d?= =?UTF-8?q?ebug=20why=20I=20can=E2=80=99t=20push.))?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mac/AppDelegate.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 79296e81d..a04644019 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -107,7 +107,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } func logDebugMessage(_ message: String) { - logMessage(message, type: .debug) } From 7cf9ae3229e499590b7cecf58d97f44da2d47982 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 6 Jan 2020 18:24:57 -0800 Subject: [PATCH 08/11] Delete a blank line. (Maybe got pushing working now?) --- Mac/AppDelegate.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index a04644019..2d3d0c244 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -111,7 +111,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } func showAddFolderSheetOnWindow(_ window: NSWindow) { - addFolderWindowController = AddFolderWindowController() addFolderWindowController!.runSheetOnWindow(window) } From 2176ebece29e9590678f16ba34c8aeabedae66a6 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 6 Jan 2020 18:50:28 -0800 Subject: [PATCH 09/11] Bump build to 25. --- xcconfig/common/NetNewsWire_ios_target_common.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index ed3b48168..512bdb5ed 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it MARKETING_VERSION = 5.0 -CURRENT_PROJECT_VERSION = 24 +CURRENT_PROJECT_VERSION = 25 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon From 746d061dcfd25579977fa6ef5a3c34eb0d9043dc Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 6 Jan 2020 21:23:39 -0700 Subject: [PATCH 10/11] Refresh UI (including progress indicator) on Timeline when coming out of suspend. --- iOS/MasterTimeline/MasterTimelineViewController.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 45d327c07..46c476e8e 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -51,6 +51,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange), name: .DisplayNameDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) // Setup the Search Controller searchController.delegate = self @@ -448,6 +449,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } } + @objc func willEnterForeground(_ note: Notification) { + updateUI() + } + @objc func scrollPositionDidChange() { coordinator.timelineMiddleIndexPath = tableView.middleVisibleRow() } From 97b188bac3fe1c8c34a52729d8310ca961753f0f Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 6 Jan 2020 20:27:53 -0800 Subject: [PATCH 11/11] Bump build to 26. --- xcconfig/common/NetNewsWire_ios_target_common.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index 512bdb5ed..024aa1a21 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it MARKETING_VERSION = 5.0 -CURRENT_PROJECT_VERSION = 25 +CURRENT_PROJECT_VERSION = 26 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon