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 + }) + } + } 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) } diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 79296e81d..2d3d0c244 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -107,12 +107,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } func logDebugMessage(_ message: String) { - logMessage(message, type: .debug) } func showAddFolderSheetOnWindow(_ window: NSWindow) { - addFolderWindowController = AddFolderWindowController() addFolderWindowController!.runSheetOnWindow(window) } 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/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 e579a5ab0..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 @@ - - - - - - - - @@ -367,13 +362,13 @@ - + diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 33702af2a..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 @@ -1101,7 +1104,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/MasterFeed/RefreshProgressView.swift b/iOS/MasterFeed/RefreshProgressView.swift index f3b22cd3e..b793d276d 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 { @@ -72,20 +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 { - lastLabelDisplayedTime = nil 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) + } } } } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index b5b7baba2..ffdc451a3 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -52,6 +52,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 @@ -451,6 +452,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } } + @objc func willEnterForeground(_ note: Notification) { + updateUI() + } + @objc func scrollPositionDidChange() { coordinator.timelineMiddleIndexPath = tableView.middleVisibleRow() } @@ -770,7 +775,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 diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index ed3b48168..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 = 24 +CURRENT_PROJECT_VERSION = 26 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon