From ac03992430332c549f36727f11b89b62383c754b Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 10:46:24 -0500 Subject: [PATCH 01/24] Fix bug in arrow key navigation where focus could get stolen from the timeline --- iOS/Detail/DetailViewController.swift | 4 ---- iOS/SceneCoordinator.swift | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 83ee0762c..1cd79b0fd 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -31,10 +31,6 @@ class DetailViewController: UIViewController { return keyboardManager.keyCommands } - override var canBecomeFirstResponder: Bool { - return true - } - deinit { webView.removeFromSuperview() DetailViewControllerWebViewProvider.shared.enqueueWebView(webView) diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index c9a458735..ac7133745 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -819,10 +819,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func navigateToTimeline() { - masterTimelineViewController?.focus() if currentArticleIndexPath == nil { selectArticle(IndexPath(row: 0, section: 0)) } + masterTimelineViewController?.focus() } func navigateToDetail() { From efd17502e08e17aebb77efa96c9db52488342823 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 11:29:04 -0500 Subject: [PATCH 02/24] Stub out all the global shortcut functions and move global shortcuts to the RootSplitViewController --- iOS/KeyboardManager.swift | 16 ++++---------- iOS/RootSplitViewController.swift | 36 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift index 7bef03e86..d9729dfb3 100644 --- a/iOS/KeyboardManager.swift +++ b/iOS/KeyboardManager.swift @@ -30,21 +30,13 @@ class KeyboardManager { private extension KeyboardManager { func load(type: KeyboardType) { - let globalFile = Bundle.main.path(forResource: KeyboardType.global.rawValue, ofType: "plist")! - let globalEntries = NSArray(contentsOfFile: globalFile)! as! [[String: Any]] - var globalCommands = globalEntries.compactMap { createKeyCommand(keyEntry: $0) } - - let specificFile = Bundle.main.path(forResource: type.rawValue, ofType: "plist")! - let specificEntries = NSArray(contentsOfFile: specificFile)! as! [[String: Any]] - let specificCommands = specificEntries.compactMap { createKeyCommand(keyEntry: $0) } - - globalCommands.append(contentsOf: specificCommands) + let file = Bundle.main.path(forResource: type.rawValue, ofType: "plist")! + let entries = NSArray(contentsOfFile: file)! as! [[String: Any]] + keyCommands = entries.compactMap { createKeyCommand(keyEntry: $0) } if type == .sidebar { - globalCommands.append(contentsOf: sidebarAuxilaryKeyCommands()) + keyCommands?.append(contentsOf: sidebarAuxilaryKeyCommands()) } - - keyCommands = globalCommands } func createKeyCommand(keyEntry: [String: Any]) -> UIKeyCommand? { diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index fe63a7d28..6e4750c29 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -12,7 +12,43 @@ class RootSplitViewController: UISplitViewController { var coordinator: SceneCoordinator! + lazy var keyboardManager = KeyboardManager(type: .global, coordinator: coordinator) + override var keyCommands: [UIKeyCommand]? { + return keyboardManager.keyCommands + } + // MARK: Keyboard Shortcuts + @objc func scrollOrGoToNextUnread(_ sender: Any?) { + } + + @objc func goToPreviousUnread(_ sender: Any?) { + } + + @objc func nextUnread(_ sender: Any?) { + } + + @objc func markRead(_ sender: Any?) { + } + + @objc func markUnreadAndGoToNextUnread(_ sender: Any?) { + } + + @objc func markAllAsReadAndGoToNextUnread(_ sender: Any?) { + } + + @objc func markOlderArticlesAsRead(_ sender: Any?) { + } + + @objc func markUnread(_ sender: Any?) { + } + + @objc func goToPreviousSubscription(_ sender: Any?) { + coordinator.selectPrevFeed() + } + + @objc func goToNextSubscription(_ sender: Any?) { + coordinator.selectNextFeed() + } @objc func openInBrowser(_ sender: Any?) { coordinator.showBrowserForCurrentArticle() From 8a5ae8c2a827dc150e50055d31f843023bc917f0 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 11:29:49 -0500 Subject: [PATCH 03/24] Remove the title from a couple of shortcuts. The shortcuts still work, they just don't show in the discovery view. --- Shared/Resources/GlobalKeyboardShortcuts.plist | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Shared/Resources/GlobalKeyboardShortcuts.plist b/Shared/Resources/GlobalKeyboardShortcuts.plist index c5117654e..ee971ae4c 100644 --- a/Shared/Resources/GlobalKeyboardShortcuts.plist +++ b/Shared/Resources/GlobalKeyboardShortcuts.plist @@ -19,8 +19,6 @@ goToPreviousUnread: - title - Go to Previous Unread key [uparrow] shiftModifier @@ -29,16 +27,12 @@ goToPreviousUnread: - title - Next Unread key + action nextUnread: - title - Next Unread key + shiftModifier From ce3ec10c958fb5d7d43cb9d71d8b1cab44e1914c Mon Sep 17 00:00:00 2001 From: Brian Warren Date: Thu, 5 Sep 2019 10:59:16 -0700 Subject: [PATCH 04/24] Enforcing automatic height for images in the template --- Mac/MainWindow/Detail/styleSheet.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mac/MainWindow/Detail/styleSheet.css b/Mac/MainWindow/Detail/styleSheet.css index 6cbf20640..dd821b6b3 100644 --- a/Mac/MainWindow/Detail/styleSheet.css +++ b/Mac/MainWindow/Detail/styleSheet.css @@ -124,7 +124,7 @@ pre { } img, figure, video, iframe { max-width: 100%; - height: auto; + height: auto !important; margin: 0 auto; } From ac37443dbbcee85fd7e79d775c1816c00d58f546 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 13:14:14 -0500 Subject: [PATCH 05/24] Stubbed out menu item keyboard shortcuts and implemented the numbered ones --- iOS/KeyboardManager.swift | 34 ++++++++++++++++++- iOS/MasterFeed/MasterFeedViewController.swift | 34 +++++++++---------- iOS/RootSplitViewController.swift | 23 +++++++++++++ iOS/SceneCoordinator.swift | 18 ++++++++++ 4 files changed, 90 insertions(+), 19 deletions(-) diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift index d9729dfb3..4163c5a74 100644 --- a/iOS/KeyboardManager.swift +++ b/iOS/KeyboardManager.swift @@ -34,8 +34,13 @@ private extension KeyboardManager { let entries = NSArray(contentsOfFile: file)! as! [[String: Any]] keyCommands = entries.compactMap { createKeyCommand(keyEntry: $0) } - if type == .sidebar { + switch type { + case .global: + keyCommands?.append(contentsOf: globalAuxilaryKeyCommands()) + case .sidebar: keyCommands?.append(contentsOf: sidebarAuxilaryKeyCommands()) + default: + break } } @@ -108,6 +113,33 @@ private extension KeyboardManager { return flags } + func globalAuxilaryKeyCommands() -> [UIKeyCommand] { + var keys = [UIKeyCommand]() + + let addNewFeedTitle = NSLocalizedString("New Feed", comment: "New Feed") + keys.append(createKeyCommand(title: addNewFeedTitle, action: "addNewFeed:", input: "n", modifiers: [.command])) + + let addNewFolderTitle = NSLocalizedString("New Folder", comment: "New Folder") + keys.append(createKeyCommand(title: addNewFolderTitle, action: "addNewFolder:", input: "n", modifiers: [.command, .shift])) + + let refreshTitle = NSLocalizedString("Refresh", comment: "Refresh") + keys.append(createKeyCommand(title: refreshTitle, action: "refresh:", input: "r", modifiers: [.command])) + + let nextUnreadTitle = NSLocalizedString("Next Unread", comment: "Next Unread") + keys.append(createKeyCommand(title: nextUnreadTitle, action: "nextUnread:", input: "/", modifiers: [.command])) + + let goToTodayTitle = NSLocalizedString("Go To Today", comment: "Go To Today") + keys.append(createKeyCommand(title: goToTodayTitle, action: "goToToday:", input: "1", modifiers: [.command])) + + let goToAllUnreadTitle = NSLocalizedString("Go To All Unread", comment: "Go To All Unread") + keys.append(createKeyCommand(title: goToAllUnreadTitle, action: "goToAllUnread:", input: "2", modifiers: [.command])) + + let goToStarredTitle = NSLocalizedString("Go To Starred", comment: "Go To Starred") + keys.append(createKeyCommand(title: goToStarredTitle, action: "goToStarred:", input: "3", modifiers: [.command])) + + return keys + } + func sidebarAuxilaryKeyCommands() -> [UIKeyCommand] { var keys = [UIKeyCommand]() diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 301401607..81c34af79 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -421,6 +421,21 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } } + func ensureSectionIsExpanded(_ sectionIndex: Int, completion: (() -> Void)? = nil) { + guard let sectionNode = coordinator.rootNode.childAtIndex(sectionIndex) else { + return + } + + if !coordinator.isExpanded(sectionNode) { + coordinator.expand(section: sectionIndex) + self.applyChanges(animate: true) { + completion?() + } + } else { + completion?() + } + } + func discloseFeed(_ feed: Feed, completion: (() -> Void)? = nil) { guard let node = coordinator.rootNode.descendantNodeRepresentingObject(feed as AnyObject) else { @@ -481,12 +496,10 @@ private extension MasterFeedViewController { } func reloadNode(_ node: Node) { - let savedNode = selectedNode() - var snapshot = dataSource.snapshot() snapshot.reloadItems([node]) dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in - self?.selectRow(node: savedNode) + self?.restoreSelectionIfNecessary() } } @@ -504,21 +517,6 @@ private extension MasterFeedViewController { completion?() } } - - func selectedNode() -> Node? { - if let selectedIndexPath = tableView.indexPathForSelectedRow { - return coordinator.nodeFor(selectedIndexPath) - } else { - return nil - } - } - - func selectRow(node: Node?) { - if let nodeToSelect = node, let selectingIndexPath = coordinator.indexPathFor(nodeToSelect) { - tableView.selectRow(at: selectingIndexPath, animated: false, scrollPosition: .none) - } - - } func makeDataSource() -> UITableViewDiffableDataSource { return MasterFeedDataSource(coordinator: coordinator, errorHandler: ErrorHandler.present(self), tableView: tableView, cellProvider: { [weak self] tableView, indexPath, node in diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 6e4750c29..5653c267c 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -7,6 +7,7 @@ // import UIKit +import Account class RootSplitViewController: UISplitViewController { @@ -54,4 +55,26 @@ class RootSplitViewController: UISplitViewController { coordinator.showBrowserForCurrentArticle() } + @objc func addNewFeed(_ sender: Any?) { + } + + @objc func addNewFolder(_ sender: Any?) { + } + + @objc func refresh(_ sender: Any?) { + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present(self)) + } + + @objc func goToToday(_ sender: Any?) { + coordinator.selectTodayFeed() + } + + @objc func goToAllUnread(_ sender: Any?) { + coordinator.selectAllUnreadFeed() + } + + @objc func goToStarred(_ sender: Any?) { + coordinator.selectStarredFeed() + } + } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index ac7133745..40cca25c8 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -571,6 +571,24 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { selectFeed(indexPath) } } + + func selectTodayFeed() { + masterFeedViewController?.ensureSectionIsExpanded(0) { + self.selectFeed(IndexPath(row: 0, section: 0)) + } + } + + func selectAllUnreadFeed() { + masterFeedViewController?.ensureSectionIsExpanded(0) { + self.selectFeed(IndexPath(row: 1, section: 0)) + } + } + + func selectStarredFeed() { + masterFeedViewController?.ensureSectionIsExpanded(0) { + self.selectFeed(IndexPath(row: 2, section: 0)) + } + } func selectArticle(_ indexPath: IndexPath?, automated: Bool = true) { currentArticleIndexPath = indexPath From 988ec75d75982fbeb8befa138ad1506296a1140f Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 13:21:14 -0500 Subject: [PATCH 06/24] Added Brian Warren's image stretching fix to the iOS app --- iOS/Resources/styleSheet.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Resources/styleSheet.css b/iOS/Resources/styleSheet.css index 2a8be9316..9c1a7fcf1 100644 --- a/iOS/Resources/styleSheet.css +++ b/iOS/Resources/styleSheet.css @@ -122,7 +122,7 @@ pre { } img, figure, video, iframe { max-width: 100%; - height: auto; + height: auto !important; margin: 0 auto; } From 73828e51154e17048ed9da3cbd313a743d9d8155 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 14:37:07 -0500 Subject: [PATCH 07/24] Remove the coordinator dependency from KeyboadManager as it wasn't needed --- iOS/Detail/DetailViewController.swift | 2 +- iOS/KeyboardManager.swift | 16 +++++----------- iOS/MasterFeed/MasterFeedViewController.swift | 2 +- .../MasterTimelineViewController.swift | 2 +- iOS/RootSplitViewController.swift | 2 +- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 1cd79b0fd..dde3bdd68 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -26,7 +26,7 @@ class DetailViewController: UIViewController { weak var coordinator: SceneCoordinator! - lazy var keyboardManager = KeyboardManager(type: .detail, coordinator: coordinator) + private let keyboardManager = KeyboardManager(type: .detail) override var keyCommands: [UIKeyCommand]? { return keyboardManager.keyCommands } diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift index 4163c5a74..a1170bce0 100644 --- a/iOS/KeyboardManager.swift +++ b/iOS/KeyboardManager.swift @@ -17,19 +17,9 @@ enum KeyboardType: String { class KeyboardManager { - private let coordinator: SceneCoordinator private(set) var keyCommands: [UIKeyCommand]? - init(type: KeyboardType, coordinator: SceneCoordinator) { - self.coordinator = coordinator - load(type: type) - } - -} - -private extension KeyboardManager { - - func load(type: KeyboardType) { + init(type: KeyboardType) { let file = Bundle.main.path(forResource: type.rawValue, ofType: "plist")! let entries = NSArray(contentsOfFile: file)! as! [[String: Any]] keyCommands = entries.compactMap { createKeyCommand(keyEntry: $0) } @@ -44,6 +34,10 @@ private extension KeyboardManager { } } +} + +private extension KeyboardManager { + func createKeyCommand(keyEntry: [String: Any]) -> UIKeyCommand? { guard let input = createKeyCommandInput(keyEntry: keyEntry) else { return nil } let modifiers = createKeyModifierFlags(keyEntry: keyEntry) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 81c34af79..8a16f7ae5 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -22,7 +22,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { var undoableCommands = [UndoableCommand]() weak var coordinator: SceneCoordinator! - lazy var keyboardManager = KeyboardManager(type: .sidebar, coordinator: coordinator) + private let keyboardManager = KeyboardManager(type: .sidebar) override var keyCommands: [UIKeyCommand]? { return keyboardManager.keyCommands } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 0b6ab4efd..96d8f6e26 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -24,7 +24,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner weak var coordinator: SceneCoordinator! var undoableCommands = [UndoableCommand]() - lazy var keyboardManager = KeyboardManager(type: .timeline, coordinator: coordinator) + private let keyboardManager = KeyboardManager(type: .timeline) override var keyCommands: [UIKeyCommand]? { return keyboardManager.keyCommands } diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 5653c267c..d5726eaa0 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -13,7 +13,7 @@ class RootSplitViewController: UISplitViewController { var coordinator: SceneCoordinator! - lazy var keyboardManager = KeyboardManager(type: .global, coordinator: coordinator) + private let keyboardManager = KeyboardManager(type: .global) override var keyCommands: [UIKeyCommand]? { return keyboardManager.keyCommands } From 7582ade6f546ab548c61498ece3d3c7a6b615b8f Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 14:50:05 -0500 Subject: [PATCH 08/24] Implement more keyboard shortcuts --- iOS/RootSplitViewController.swift | 8 ++++++++ iOS/SceneCoordinator.swift | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index d5726eaa0..278e0c6a7 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -26,21 +26,29 @@ class RootSplitViewController: UISplitViewController { } @objc func nextUnread(_ sender: Any?) { + coordinator.selectNextUnread() } @objc func markRead(_ sender: Any?) { + coordinator.markAsReadForCurrentArticle() } @objc func markUnreadAndGoToNextUnread(_ sender: Any?) { + coordinator.markAsUnreadForCurrentArticle() + coordinator.selectNextUnread() } @objc func markAllAsReadAndGoToNextUnread(_ sender: Any?) { + coordinator.markAllAsReadInTimeline() + coordinator.selectNextUnread() } @objc func markOlderArticlesAsRead(_ sender: Any?) { + coordinator.markAsReadOlderArticlesInTimeline() } @objc func markUnread(_ sender: Any?) { + coordinator.markAsUnreadForCurrentArticle() } @objc func goToPreviousSubscription(_ sender: Any?) { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 40cca25c8..b6519fa4e 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -731,6 +731,11 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { masterNavigationController.popViewController(animated: true) } + func markAsReadOlderArticlesInTimeline() { + if let indexPath = currentArticleIndexPath { + markAsReadOlderArticlesInTimeline(indexPath) + } + } func markAsReadOlderArticlesInTimeline(_ indexPath: IndexPath) { let article = articles[indexPath.row] let articlesToMark = articles.filter { $0.logicalDatePublished < article.logicalDatePublished } @@ -740,6 +745,18 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { markAllAsRead(articlesToMark) } + func markAsReadForCurrentArticle() { + if let article = currentArticle { + markArticles(Set([article]), statusKey: .read, flag: true) + } + } + + func markAsUnreadForCurrentArticle() { + if let article = currentArticle { + markArticles(Set([article]), statusKey: .read, flag: false) + } + } + func toggleReadForCurrentArticle() { if let article = currentArticle { markArticles(Set([article]), statusKey: .read, flag: !article.status.read) From afce6ff26fbd78d515c89935534f22021f306b5c Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 15:07:35 -0500 Subject: [PATCH 09/24] Add keyboard shortcuts for Add Feed and Add Folder --- NetNewsWire.xcodeproj/project.pbxproj | 26 +++++++------------ iOS/Add/{UIKit => }/Add.storyboard | 0 .../AddContainerViewController.swift | 15 ++++++++--- iOS/Add/AddControllerType.swift | 14 ++++++++++ .../{UIKit => }/AddFeedFolderPickerData.swift | 0 .../{UIKit => }/AddFeedViewController.swift | 1 + .../{UIKit => }/AddFolderViewController.swift | 2 ++ iOS/Add/AddView.swift | 23 ---------------- iOS/MasterFeed/MasterFeedViewController.swift | 2 +- iOS/RootSplitViewController.swift | 2 ++ iOS/SceneCoordinator.swift | 6 +++-- iOS/SceneDelegate.swift | 2 +- 12 files changed, 45 insertions(+), 48 deletions(-) rename iOS/Add/{UIKit => }/Add.storyboard (100%) rename iOS/Add/{UIKit => }/AddContainerViewController.swift (93%) create mode 100644 iOS/Add/AddControllerType.swift rename iOS/Add/{UIKit => }/AddFeedFolderPickerData.swift (100%) rename iOS/Add/{UIKit => }/AddFeedViewController.swift (99%) rename iOS/Add/{UIKit => }/AddFolderViewController.swift (98%) delete mode 100644 iOS/Add/AddView.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 5bd756139..5ca775c33 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 5110AB7822B7BD6200A94F76 /* AddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5110AB7722B7BD6200A94F76 /* AddView.swift */; }; 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; }; 511D43CF231FA62200FB1562 /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; @@ -34,6 +33,7 @@ 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */; }; 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; }; + 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */; }; 51543685228F6753005E1CDF /* DetailAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51543684228F6753005E1CDF /* DetailAccountViewController.swift */; }; 515436882291D75D005E1CDF /* AddLocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515436872291D75D005E1CDF /* AddLocalAccountViewController.swift */; }; 5154368A2291FED9005E1CDF /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515436892291FED9005E1CDF /* FeedbinAccountViewController.swift */; }; @@ -686,7 +686,6 @@ 510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLocalAccountView.swift; sourceTree = ""; }; 510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeedbinAccountView.swift; sourceTree = ""; }; 510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountLabelView.swift; sourceTree = ""; }; - 5110AB7722B7BD6200A94F76 /* AddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddView.swift; sourceTree = ""; }; 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = ""; }; 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = ""; }; 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = ""; }; @@ -707,6 +706,7 @@ 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedbinWindowController.swift; sourceTree = ""; }; 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = ""; }; 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = ""; }; + 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddControllerType.swift; sourceTree = ""; }; 51543684228F6753005E1CDF /* DetailAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailAccountViewController.swift; sourceTree = ""; }; 515436872291D75D005E1CDF /* AddLocalAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddLocalAccountViewController.swift; sourceTree = ""; }; 515436892291FED9005E1CDF /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = ""; }; @@ -1028,18 +1028,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 5110AB6E22B7BD3C00A94F76 /* UIKit */ = { - isa = PBXGroup; - children = ( - 51C452822265093600C03939 /* Add.storyboard */, - 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */, - 51C452842265093600C03939 /* AddFeedViewController.swift */, - 51C452812265093600C03939 /* AddFeedFolderPickerData.swift */, - 51C4528B2265095F00C03939 /* AddFolderViewController.swift */, - ); - path = UIKit; - sourceTree = ""; - }; 511D43CE231FA51100FB1562 /* Resources */ = { isa = PBXGroup; children = ( @@ -1217,8 +1205,12 @@ 51C452802265093600C03939 /* Add */ = { isa = PBXGroup; children = ( - 5110AB6E22B7BD3C00A94F76 /* UIKit */, - 5110AB7722B7BD6200A94F76 /* AddView.swift */, + 51C452822265093600C03939 /* Add.storyboard */, + 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */, + 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */, + 51C452842265093600C03939 /* AddFeedViewController.swift */, + 51C452812265093600C03939 /* AddFeedFolderPickerData.swift */, + 51C4528B2265095F00C03939 /* AddFolderViewController.swift */, ); path = Add; sourceTree = ""; @@ -2434,7 +2426,6 @@ files = ( 840D617F2029031C009BC708 /* AppDelegate.swift in Sources */, 512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */, - 5110AB7822B7BD6200A94F76 /* AddView.swift in Sources */, 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */, 51EF0F79227716380050506E /* ColorHash.swift in Sources */, 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */, @@ -2477,6 +2468,7 @@ 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */, 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */, 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */, + 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */, 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */, 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */, 51EF0F7C2277919E0050506E /* TimelineNumberOfLinesViewController.swift in Sources */, diff --git a/iOS/Add/UIKit/Add.storyboard b/iOS/Add/Add.storyboard similarity index 100% rename from iOS/Add/UIKit/Add.storyboard rename to iOS/Add/Add.storyboard diff --git a/iOS/Add/UIKit/AddContainerViewController.swift b/iOS/Add/AddContainerViewController.swift similarity index 93% rename from iOS/Add/UIKit/AddContainerViewController.swift rename to iOS/Add/AddContainerViewController.swift index 16d92719c..dc574c02f 100644 --- a/iOS/Add/UIKit/AddContainerViewController.swift +++ b/iOS/Add/AddContainerViewController.swift @@ -33,6 +33,7 @@ class AddContainerViewController: UIViewController { private var currentViewController: AddContainerViewControllerChild? + var initialControllerType: AddControllerType? var initialFeed: String? var initialFeedName: String? @@ -40,20 +41,26 @@ class AddContainerViewController: UIViewController { super.viewDidLoad() activityIndicatorView.isHidden = true - - switchToFeed() + + typeSelectorSegmentedControl.selectedSegmentIndex = initialControllerType?.rawValue ?? 0 + switch initialControllerType { + case .feed: + switchToFeed() + case .folder: + switchToFolder() + default: + assertionFailure() + } } @IBAction func typeSelectorChanged(_ sender: UISegmentedControl) { - switch sender.selectedSegmentIndex { case 0: switchToFeed() default: switchToFolder() } - } @IBAction func cancel(_ sender: Any) { diff --git a/iOS/Add/AddControllerType.swift b/iOS/Add/AddControllerType.swift new file mode 100644 index 000000000..cf68e97af --- /dev/null +++ b/iOS/Add/AddControllerType.swift @@ -0,0 +1,14 @@ +// +// AddControllerType.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 9/5/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation + +enum AddControllerType: Int { + case feed = 0 + case folder = 1 +} diff --git a/iOS/Add/UIKit/AddFeedFolderPickerData.swift b/iOS/Add/AddFeedFolderPickerData.swift similarity index 100% rename from iOS/Add/UIKit/AddFeedFolderPickerData.swift rename to iOS/Add/AddFeedFolderPickerData.swift diff --git a/iOS/Add/UIKit/AddFeedViewController.swift b/iOS/Add/AddFeedViewController.swift similarity index 99% rename from iOS/Add/UIKit/AddFeedViewController.swift rename to iOS/Add/AddFeedViewController.swift index 2d6295c62..a6736dbba 100644 --- a/iOS/Add/UIKit/AddFeedViewController.swift +++ b/iOS/Add/AddFeedViewController.swift @@ -44,6 +44,7 @@ class AddFeedViewController: UITableViewController, AddContainerViewControllerCh urlTextField.autocapitalizationType = .none urlTextField.text = initialFeed urlTextField.delegate = self + urlTextField.becomeFirstResponder() if initialFeed != nil { delegate?.readyToAdd(state: true) diff --git a/iOS/Add/UIKit/AddFolderViewController.swift b/iOS/Add/AddFolderViewController.swift similarity index 98% rename from iOS/Add/UIKit/AddFolderViewController.swift rename to iOS/Add/AddFolderViewController.swift index 3e7849937..77844d460 100644 --- a/iOS/Add/UIKit/AddFolderViewController.swift +++ b/iOS/Add/AddFolderViewController.swift @@ -31,6 +31,8 @@ class AddFolderViewController: UITableViewController, AddContainerViewController accounts = AccountManager.shared.sortedActiveAccounts nameTextField.delegate = self + nameTextField.becomeFirstResponder() + accountLabel.text = (accounts[0] as DisplayNameProvider).nameForDisplay if shouldDisplayPicker { diff --git a/iOS/Add/AddView.swift b/iOS/Add/AddView.swift deleted file mode 100644 index 0c57b1a97..000000000 --- a/iOS/Add/AddView.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// AddView.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 6/17/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct AddView : View { - var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/) - } -} - -#if DEBUG -struct AddView_Previews : PreviewProvider { - static var previews: some View { - AddView() - } -} -#endif diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 8a16f7ae5..ed86b3da7 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -347,7 +347,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } @IBAction func add(_ sender: UIBarButtonItem) { - coordinator.showAdd() + coordinator.showAdd(.feed) } @objc func toggleSectionHeader(_ sender: UITapGestureRecognizer) { diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 278e0c6a7..7f862138a 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -64,9 +64,11 @@ class RootSplitViewController: UISplitViewController { } @objc func addNewFeed(_ sender: Any?) { + coordinator.showAdd(.feed) } @objc func addNewFolder(_ sender: Any?) { + coordinator.showAdd(.folder) } @objc func refresh(_ sender: Any?) { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index b6519fa4e..ad32518b9 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -805,8 +805,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { // self.present(settings, animated: true) } - func showAdd() { - let addViewController = UIStoryboard.add.instantiateInitialViewController()! + func showAdd(_ type: AddControllerType) { + let addViewController = UIStoryboard.add.instantiateInitialViewController() as! UINavigationController + let containerController = addViewController.topViewController as! AddContainerViewController + containerController.initialControllerType = type addViewController.modalPresentationStyle = .formSheet addViewController.preferredContentSize = AddContainerViewController.preferredContentSizeForFormSheetDisplay masterFeedViewController.present(addViewController, animated: true) diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index dc496dff8..4cdc78b5c 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -66,7 +66,7 @@ private extension SceneDelegate { case "com.ranchero.NetNewsWire.ShowSearch": coordinator.showSearch() case "com.ranchero.NetNewsWire.ShowAdd": - coordinator.showAdd() + coordinator.showAdd(.feed) default: break } From 9a52834b7abd23ef0141860f0fe68939204e1cd5 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 15:43:01 -0500 Subject: [PATCH 10/24] Implement shortcuts found in the Articles menu of NNW for Mac --- iOS/Detail/DetailViewController.swift | 2 +- iOS/KeyboardManager.swift | 15 +++++++++++++++ iOS/RootSplitViewController.swift | 8 ++++++++ iOS/SceneCoordinator.swift | 2 +- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index dde3bdd68..78a14b744 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -151,7 +151,7 @@ class DetailViewController: UIViewController { } @IBAction func toggleStar(_ sender: Any) { - coordinator.toggleStarForCurrentArticle() + coordinator.toggleStarredForCurrentArticle() } @IBAction func openBrowser(_ sender: Any) { diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift index a1170bce0..95ac2f505 100644 --- a/iOS/KeyboardManager.swift +++ b/iOS/KeyboardManager.swift @@ -131,6 +131,21 @@ private extension KeyboardManager { let goToStarredTitle = NSLocalizedString("Go To Starred", comment: "Go To Starred") keys.append(createKeyCommand(title: goToStarredTitle, action: "goToStarred:", input: "3", modifiers: [.command])) + let toggleReadTitle = NSLocalizedString("Toggle Read Status", comment: "Toggle Read Status") + keys.append(createKeyCommand(title: toggleReadTitle, action: "toggleRead:", input: "U", modifiers: [.command, .shift])) + + let markAllAsReadTitle = NSLocalizedString("Mark All as Read", comment: "Mark All as Read") + keys.append(createKeyCommand(title: markAllAsReadTitle, action: "markAllAsRead:", input: "k", modifiers: [.command])) + + let markOlderAsReadTitle = NSLocalizedString("Mark Older as Read", comment: "Mark Older as Read") + keys.append(createKeyCommand(title: markOlderAsReadTitle, action: "markOlderArticlesAsRead:", input: "k", modifiers: [.command, .shift])) + + let toggleStarredTitle = NSLocalizedString("Toggle Starred Status", comment: "Toggle Starred Status") + keys.append(createKeyCommand(title: toggleStarredTitle, action: "toggleStarred:", input: "l", modifiers: [.command, .shift])) + + let openInBrowserTitle = NSLocalizedString("Open In Browser", comment: "Open In Browser") + keys.append(createKeyCommand(title: openInBrowserTitle, action: "openInBrowser:", input: UIKeyCommand.inputRightArrow, modifiers: [.command])) + return keys } diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 7f862138a..2c4cb1171 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -87,4 +87,12 @@ class RootSplitViewController: UISplitViewController { coordinator.selectStarredFeed() } + @objc func toggleRead(_ sender: Any?) { + coordinator.toggleReadForCurrentArticle() + } + + @objc func toggleStarred(_ sender: Any?) { + coordinator.toggleStarredForCurrentArticle() + } + } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index ad32518b9..a7c655e07 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -772,7 +772,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { runCommand(markReadCommand) } - func toggleStarForCurrentArticle() { + func toggleStarredForCurrentArticle() { if let article = currentArticle { markArticles(Set([article]), statusKey: .starred, flag: !article.status.starred) } From c9fd7d01d9865be3486fbad6e91b75932f9c8504 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 15:54:58 -0500 Subject: [PATCH 11/24] Add delete feed keyboard shortcut --- Shared/Resources/SidebarKeyboardShortcuts.plist | 4 ++-- iOS/MasterFeed/MasterFeedViewController.swift | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Shared/Resources/SidebarKeyboardShortcuts.plist b/Shared/Resources/SidebarKeyboardShortcuts.plist index f34f1d8d7..9ad1d2c5f 100644 --- a/Shared/Resources/SidebarKeyboardShortcuts.plist +++ b/Shared/Resources/SidebarKeyboardShortcuts.plist @@ -10,7 +10,7 @@ title - Collapse Selected Rows + Collapse Selected Row key , action @@ -18,7 +18,7 @@ title - Expand Selected Rows + Expand Selected Row key . action diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index ed86b3da7..36f352fd9 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -398,6 +398,12 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { coordinator.showBrowserForCurrentFeed() } + @objc override func delete(_ sender: Any?) { + if let indexPath = coordinator.currentFeedIndexPath { + delete(indexPath: indexPath) + } + } + // MARK: API func updateFeedSelection() { From 953259f0d3778df1192d05dcbb65cb95f14df39e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 16:04:07 -0500 Subject: [PATCH 12/24] Add selected row expanding and collapsing keyboard shortcuts --- iOS/MasterFeed/MasterFeedViewController.swift | 14 ++++++++++++++ iOS/SceneCoordinator.swift | 2 ++ 2 files changed, 16 insertions(+) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 36f352fd9..048dfb94a 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -404,6 +404,20 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } } + @objc func expandSelectedRows(_ sender: Any?) { + if let indexPath = coordinator.currentFeedIndexPath { + coordinator.expand(indexPath) + self.applyChanges(animate: true) + } + } + + @objc func collapseSelectedRows(_ sender: Any?) { + if let indexPath = coordinator.currentFeedIndexPath { + coordinator.collapse(indexPath) + self.applyChanges(animate: true) + } + } + // MARK: API func updateFeedSelection() { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index a7c655e07..57ca14d14 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -465,6 +465,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { func expand(_ indexPath: IndexPath) { let expandNode = shadowTable[indexPath.section][indexPath.row] + guard !expandedNodes.contains(expandNode) else { return } expandedNodes.append(expandNode) animatingChanges = true @@ -499,6 +500,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { animatingChanges = true let collapseNode = shadowTable[indexPath.section][indexPath.row] + guard expandedNodes.contains(collapseNode) else { return } if let removeNode = expandedNodes.firstIndex(of: collapseNode) { expandedNodes.remove(at: removeNode) } From 734ce4bc67ac3e3ef6c65e78d736d8429bd41822 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 16:08:57 -0500 Subject: [PATCH 13/24] Rename expand and collapse functions to be more descriptive --- iOS/MasterFeed/MasterFeedViewController.swift | 16 ++++++++-------- iOS/SceneCoordinator.swift | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 048dfb94a..b80dbe217 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -361,11 +361,11 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { if coordinator.isExpanded(sectionNode) { headerView.disclosureExpanded = false - coordinator.collapse(section: sectionIndex) + coordinator.collapseSection(sectionIndex) self.applyChanges(animate: true) } else { headerView.disclosureExpanded = true - coordinator.expand(section: sectionIndex) + coordinator.expandSection(sectionIndex) self.applyChanges(animate: true) } @@ -406,14 +406,14 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { @objc func expandSelectedRows(_ sender: Any?) { if let indexPath = coordinator.currentFeedIndexPath { - coordinator.expand(indexPath) + coordinator.expandFolder(indexPath) self.applyChanges(animate: true) } } @objc func collapseSelectedRows(_ sender: Any?) { if let indexPath = coordinator.currentFeedIndexPath { - coordinator.collapse(indexPath) + coordinator.collapseFolder(indexPath) self.applyChanges(animate: true) } } @@ -447,7 +447,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } if !coordinator.isExpanded(sectionNode) { - coordinator.expand(section: sectionIndex) + coordinator.expandSection(sectionIndex) self.applyChanges(animate: true) { completion?() } @@ -473,7 +473,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { return } - coordinator.expand(indexPath) + coordinator.expandFolder(indexPath) reloadNode(parent) self.applyChanges(animate: true) { [weak self] in @@ -631,7 +631,7 @@ private extension MasterFeedViewController { guard let indexPath = tableView.indexPath(for: cell) else { return } - coordinator.expand(indexPath) + coordinator.expandFolder(indexPath) self.applyChanges(animate: true) } @@ -639,7 +639,7 @@ private extension MasterFeedViewController { guard let indexPath = tableView.indexPath(for: cell) else { return } - coordinator.collapse(indexPath) + coordinator.collapseFolder(indexPath) self.applyChanges(animate: true) } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 57ca14d14..70c418056 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -436,7 +436,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { return 0 } - func expand(section: Int) { + func expandSection(_ section: Int) { guard let expandNode = treeController.rootNode.childAtIndex(section) else { return } @@ -463,7 +463,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { animatingChanges = false } - func expand(_ indexPath: IndexPath) { + func expandFolder(_ indexPath: IndexPath) { let expandNode = shadowTable[indexPath.section][indexPath.row] guard !expandedNodes.contains(expandNode) else { return } expandedNodes.append(expandNode) @@ -480,7 +480,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { animatingChanges = false } - func collapse(section: Int) { + func collapseSection(_ section: Int) { animatingChanges = true guard let collapseNode = treeController.rootNode.childAtIndex(section) else { @@ -496,7 +496,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { animatingChanges = false } - func collapse(_ indexPath: IndexPath) { + func collapseFolder(_ indexPath: IndexPath) { animatingChanges = true let collapseNode = shadowTable[indexPath.section][indexPath.row] From 6d24ea642a969ac098ebff3065f848eff4309af2 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 16:38:33 -0500 Subject: [PATCH 14/24] Add expand and collapse all folders shortcuts --- iOS/MasterFeed/MasterFeedViewController.swift | 10 ++++++ iOS/SceneCoordinator.swift | 33 ++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index b80dbe217..e0c34774d 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -418,6 +418,16 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } } + @objc func expandAll(_ sender: Any?) { + coordinator.expandAllSectionsAndFolders() + self.applyChanges(animate: true) + } + + @objc func collapseAllExceptForGroupItems(_ sender: Any?) { + coordinator.collapseAllFolders() + self.applyChanges(animate: true) + } + // MARK: API func updateFeedSelection() { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 70c418056..7111f79c4 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -437,9 +437,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func expandSection(_ section: Int) { - guard let expandNode = treeController.rootNode.childAtIndex(section) else { + guard let expandNode = treeController.rootNode.childAtIndex(section), !expandedNodes.contains(expandNode) else { return } + expandedNodes.append(expandNode) animatingChanges = true @@ -463,6 +464,20 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { animatingChanges = false } + func expandAllSectionsAndFolders() { + for (sectionIndex, sectionNode) in treeController.rootNode.childNodes.enumerated() { + + expandSection(sectionIndex) + + for topLevelNode in sectionNode.childNodes { + if topLevelNode.representedObject is Folder, let indexPath = indexPathFor(topLevelNode) { + expandFolder(indexPath) + } + } + + } + } + func expandFolder(_ indexPath: IndexPath) { let expandNode = shadowTable[indexPath.section][indexPath.row] guard !expandedNodes.contains(expandNode) else { return } @@ -481,12 +496,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func collapseSection(_ section: Int) { - animatingChanges = true - - guard let collapseNode = treeController.rootNode.childAtIndex(section) else { + guard let collapseNode = treeController.rootNode.childAtIndex(section), expandedNodes.contains(collapseNode) else { return } + animatingChanges = true + if let removeNode = expandedNodes.firstIndex(of: collapseNode) { expandedNodes.remove(at: removeNode) } @@ -496,6 +511,16 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { animatingChanges = false } + func collapseAllFolders() { + for sectionNode in treeController.rootNode.childNodes { + for topLevelNode in sectionNode.childNodes { + if topLevelNode.representedObject is Folder, let indexPath = indexPathFor(topLevelNode) { + collapseFolder(indexPath) + } + } + } + } + func collapseFolder(_ indexPath: IndexPath) { animatingChanges = true From 18d442d90151777f26f9e4fafd7ed52c98dc4105 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 18:02:40 -0500 Subject: [PATCH 15/24] Add select previous unread keyboard shortcut --- iOS/RootSplitViewController.swift | 1 + iOS/SceneCoordinator.swift | 147 ++++++++++++++++++++++++++++-- 2 files changed, 139 insertions(+), 9 deletions(-) diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 2c4cb1171..43ac14043 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -23,6 +23,7 @@ class RootSplitViewController: UISplitViewController { } @objc func goToPreviousUnread(_ sender: Any?) { + coordinator.selectPrevUnread() } @objc func nextUnread(_ sender: Any?) { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 7111f79c4..0ebb1e038 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -717,6 +717,22 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } } + func selectPrevUnread() { + + // This should never happen, but I don't want to risk throwing us + // into an infinate loop searching for an unread that isn't there. + if appDelegate.unreadCount < 1 { + return + } + + if selectPrevUnreadArticleInTimeline() { + return + } + + selectPrevUnreadFeedFetcher() + selectPrevUnreadArticleInTimeline() + } + func selectNextUnread() { // This should never happen, but I don't want to risk throwing us @@ -1048,11 +1064,113 @@ private extension SceneCoordinator { self.showAvatars = false } - // MARK: Select Next Unread + // MARK: Select Prev Unread + @discardableResult + func selectPrevUnreadArticleInTimeline() -> Bool { + let startingRow: Int = { + if let indexPath = currentArticleIndexPath { + return indexPath.row - 1 + } else { + return articles.count - 1 + } + }() + + return selectPrevArticleInTimeline(startingRow: startingRow) + } + + func selectPrevArticleInTimeline(startingRow: Int) -> Bool { + + guard startingRow >= 0 else { + return false + } + + for i in (0...startingRow).reversed() { + let article = articles[i] + if !article.status.read { + selectArticle(IndexPath(row: i, section: 0)) + return true + } + } + + return false + + } + + func selectPrevUnreadFeedFetcher() { + + let indexPath: IndexPath = { + if currentFeedIndexPath == nil { + return IndexPath(row: 0, section: 0) + } else { + return currentFeedIndexPath! + } + }() + + // Increment or wrap around the IndexPath + let nextIndexPath: IndexPath = { + if indexPath.row - 1 < 0 { + if indexPath.section - 1 < 0 { + return IndexPath(row: shadowTable[shadowTable.count - 1].count - 1, section: shadowTable.count - 1) + } else { + return IndexPath(row: shadowTable[indexPath.section - 1].count - 1, section: indexPath.section - 1) + } + } else { + return IndexPath(row: indexPath.row - 1, section: indexPath.section) + } + }() + + if selectPrevUnreadFeedFetcher(startingWith: nextIndexPath) { + return + } + let maxIndexPath = IndexPath(row: shadowTable[shadowTable.count - 1].count - 1, section: shadowTable.count - 1) + selectPrevUnreadFeedFetcher(startingWith: maxIndexPath) + + } + + @discardableResult + func selectPrevUnreadFeedFetcher(startingWith indexPath: IndexPath) -> Bool { + + for i in (0...indexPath.section).reversed() { + + let startingRow: Int = { + if indexPath.section == i { + return indexPath.row + } else { + return shadowTable[i].count - 1 + } + }() + + for j in (0...startingRow).reversed() { + + let prevIndexPath = IndexPath(row: j, section: i) + guard let node = nodeFor(prevIndexPath), let unreadCountProvider = node.representedObject as? UnreadCountProvider else { + assertionFailure() + return true + } + + if expandedNodes.contains(node) { + continue + } + + if unreadCountProvider.unreadCount > 0 { + selectFeed(prevIndexPath) + return true + } + + } + + } + + return false + + } + + // MARK: Select Next Unread + @discardableResult func selectFirstUnreadArticleInTimeline() -> Bool { - return selectArticleInTimeline(startingRow: 0) + return selectNextArticleInTimeline(startingRow: 0) } @discardableResult @@ -1065,10 +1183,10 @@ private extension SceneCoordinator { } }() - return selectArticleInTimeline(startingRow: startingRow) + return selectNextArticleInTimeline(startingRow: startingRow) } - func selectArticleInTimeline(startingRow: Int) -> Bool { + func selectNextArticleInTimeline(startingRow: Int) -> Bool { guard startingRow < articles.count else { return false @@ -1088,10 +1206,13 @@ private extension SceneCoordinator { func selectNextUnreadFeedFetcher() { - guard let indexPath = currentFeedIndexPath else { - assertionFailure() - return - } + let indexPath: IndexPath = { + if currentFeedIndexPath == nil { + return IndexPath(row: -1, section: 0) + } else { + return currentFeedIndexPath! + } + }() // Increment or wrap around the IndexPath let nextIndexPath: IndexPath = { @@ -1118,7 +1239,15 @@ private extension SceneCoordinator { for i in indexPath.section.. Date: Thu, 5 Sep 2019 21:14:19 -0500 Subject: [PATCH 16/24] Add scroll or go to next unread keyboard shortcut --- iOS/Detail/DetailViewController.swift | 25 +++++++++++++++++++++++++ iOS/KeyboardManager.swift | 2 +- iOS/RootSplitViewController.swift | 2 ++ iOS/SceneCoordinator.swift | 8 ++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 78a14b744..309d573c7 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -184,7 +184,32 @@ class DetailViewController: UIViewController { webView.becomeFirstResponder() } + func finalScrollPosition() -> CGFloat { + return webView.scrollView.contentSize.height - webView.scrollView.bounds.size.height + webView.scrollView.contentInset.bottom + } + + func canScrollDown() -> Bool { + return webView.scrollView.contentOffset.y < finalScrollPosition() + } + + func scrollPageDown() { + let scrollToY: CGFloat = { + let fullScroll = webView.scrollView.contentOffset.y + webView.scrollView.bounds.size.height + let final = finalScrollPosition() + if fullScroll < final { + return fullScroll + } else { + return final + } + }() + + let convertedPoint = self.view.convert(CGPoint(x: 0, y: 0), to: webView.scrollView) + let scrollToPoint = CGPoint(x: convertedPoint.x, y: scrollToY) + webView.scrollView.setContentOffset(scrollToPoint, animated: true) + } + } +//print("\(candidateY) : \(webView.scrollView.contentSize.height)") class ArticleActivityItemSource: NSObject, UIActivityItemSource { diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift index 95ac2f505..b5d6742b9 100644 --- a/iOS/KeyboardManager.swift +++ b/iOS/KeyboardManager.swift @@ -60,7 +60,7 @@ private extension KeyboardManager { switch(key) { case "[space]": - return " " + return "\u{0020}" case "[uparrow]": return UIKeyCommand.inputUpArrow case "[downarrow]": diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 43ac14043..3ea657a2a 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -19,7 +19,9 @@ class RootSplitViewController: UISplitViewController { } // MARK: Keyboard Shortcuts + @objc func scrollOrGoToNextUnread(_ sender: Any?) { + coordinator.scrollOrGoToNextUnread() } @objc func goToPreviousUnread(_ sender: Any?) { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 0ebb1e038..ae584deef 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -753,6 +753,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } + func scrollOrGoToNextUnread() { + if detailViewController?.canScrollDown() ?? false { + detailViewController?.scrollPageDown() + } else { + selectNextUnread() + } + } + func markAllAsRead(_ articles: [Article]) { guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else { return From 1e7ed5bf351b6f27b2d809b3fbc62e5f0e2c808f Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Sep 2019 21:19:00 -0500 Subject: [PATCH 17/24] Make the scrolling check more succinct and readable --- iOS/Detail/DetailViewController.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 309d573c7..f60ec3bad 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -196,11 +196,7 @@ class DetailViewController: UIViewController { let scrollToY: CGFloat = { let fullScroll = webView.scrollView.contentOffset.y + webView.scrollView.bounds.size.height let final = finalScrollPosition() - if fullScroll < final { - return fullScroll - } else { - return final - } + return fullScroll < final ? fullScroll : final }() let convertedPoint = self.view.convert(CGPoint(x: 0, y: 0), to: webView.scrollView) From 9b17293f4efee320e07b2a91144ae7b4d55106c7 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 6 Sep 2019 07:29:36 -0500 Subject: [PATCH 18/24] Fix bugs in automated routing on application launch --- iOS/MasterFeed/MasterFeedViewController.swift | 7 +++- iOS/SceneCoordinator.swift | 40 +++++++++++-------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index e0c34774d..1c2c10c2e 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -259,7 +259,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { becomeFirstResponder() - coordinator.selectFeed(indexPath) + coordinator.selectFeed(indexPath, automated: false) } override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath { @@ -469,17 +469,20 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { func discloseFeed(_ feed: Feed, completion: (() -> Void)? = nil) { guard let node = coordinator.rootNode.descendantNodeRepresentingObject(feed as AnyObject) else { - return + completion?() + return } if let indexPath = coordinator.indexPathFor(node) { tableView.scrollToRow(at: indexPath, at: .middle, animated: true) coordinator.selectFeed(indexPath) + completion?() return } // It wasn't already visable, so expand its folder and try again guard let parent = node.parent, let indexPath = coordinator.indexPathFor(parent) else { + completion?() return } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index ae584deef..27aaacbd6 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -108,7 +108,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { var timelineFetcher: ArticleFetcher? { didSet { - selectArticle(nil) if timelineFetcher is Feed { showFeedNames = false } else { @@ -300,6 +299,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func handle(_ activity: NSUserActivity) { + selectFeed(nil) + guard let activityType = ActivityType(rawValue: activity.activityType) else { return } switch activityType { case .selectToday: @@ -567,24 +568,28 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { return indexes } - func selectFeed(_ indexPath: IndexPath?) { - if navControllerForTimeline().viewControllers.filter({ $0 is MasterTimelineViewController }).count < 1 { - masterTimelineViewController = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self) - masterTimelineViewController!.coordinator = self - navControllerForTimeline().pushViewController(masterTimelineViewController!, animated: true) - } - + func selectFeed(_ indexPath: IndexPath?, automated: Bool = true) { + selectArticle(nil) currentFeedIndexPath = indexPath if let ip = indexPath, let node = nodeFor(ip), let fetcher = node.representedObject as? ArticleFetcher { timelineFetcher = fetcher updateSelectingActivity(with: node) + + if navControllerForTimeline().viewControllers.filter({ $0 is MasterTimelineViewController }).count < 1 { + masterTimelineViewController = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self) + masterTimelineViewController!.coordinator = self + navControllerForTimeline().pushViewController(masterTimelineViewController!, animated: !automated) + } } else { timelineFetcher = nil + + if rootSplitViewController.isCollapsed && navControllerForTimeline().viewControllers.last is MasterTimelineViewController { + navControllerForTimeline().popViewController(animated: !automated) + } } masterFeedViewController.updateFeedSelection() - selectArticle(nil) } func selectPrevFeed() { @@ -626,9 +631,13 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } if indexPath == nil { - if !rootSplitViewController.isCollapsed { + if rootSplitViewController.isCollapsed { + if masterNavigationController.children.last is DetailViewController { + masterNavigationController.popViewController(animated: false) + } + } else { let systemMessageViewController = UIStoryboard.main.instantiateController(ofType: SystemMessageViewController.self) - installDetailController(systemMessageViewController) + installDetailController(systemMessageViewController, automated: automated) } masterTimelineViewController?.updateArticleSelection(animate: true) return @@ -637,7 +646,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { if detailViewController == nil { let detailViewController = UIStoryboard.main.instantiateController(ofType: DetailViewController.self) detailViewController.coordinator = self - installDetailController(detailViewController) + installDetailController(detailViewController, automated: automated) } // Automatically hide the overlay @@ -649,7 +658,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } if automated { - masterTimelineViewController?.updateArticleSelection(animate: true) + masterTimelineViewController?.updateArticleSelection(animate: false) } detailViewController?.updateArticleSelection() @@ -839,7 +848,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func discloseFeed(_ feed: Feed, completion: (() -> Void)? = nil) { - masterNavigationController.popViewController(animated: true) masterFeedViewController.discloseFeed(feed) { completion?() } @@ -1463,7 +1471,7 @@ private extension SceneCoordinator { // during the display mode change callback (in the split view controller delegate). To fool the // system, we leave the same controller, the shim, in place and change its child controllers instead. - func installDetailController(_ detailController: UIViewController) { + func installDetailController(_ detailController: UIViewController, automated: Bool) { let showButton = rootSplitViewController.displayMode != .allVisible let controller = addNavControllerIfNecessary(detailController, showButton: showButton) @@ -1471,7 +1479,7 @@ private extension SceneCoordinator { let targetSplit = ensureDoubleSplit().children.first as! UISplitViewController targetSplit.showDetailViewController(controller, sender: self) } else if rootSplitViewController.isCollapsed { - masterNavigationController.pushViewController(controller, animated: true) + masterNavigationController.pushViewController(controller, animated: !automated) } else { if let shimController = rootSplitViewController.viewControllers.last { shimController.replaceChildAndPinView(controller) From d6ea701d266d59c868de2b0563183754b5086b2e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 6 Sep 2019 07:58:36 -0500 Subject: [PATCH 19/24] Prevent global keyboard shortcuts from being executed in modal dialogs --- iOS/KeyboardManager.swift | 20 ++++++++++---------- iOS/RootSplitViewController.swift | 5 ----- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift index b5d6742b9..0af53e80e 100644 --- a/iOS/KeyboardManager.swift +++ b/iOS/KeyboardManager.swift @@ -20,17 +20,17 @@ class KeyboardManager { private(set) var keyCommands: [UIKeyCommand]? init(type: KeyboardType) { - let file = Bundle.main.path(forResource: type.rawValue, ofType: "plist")! - let entries = NSArray(contentsOfFile: file)! as! [[String: Any]] - keyCommands = entries.compactMap { createKeyCommand(keyEntry: $0) } + let globalFile = Bundle.main.path(forResource: KeyboardType.global.rawValue, ofType: "plist")! + let globalEntries = NSArray(contentsOfFile: globalFile)! as! [[String: Any]] + keyCommands = globalEntries.compactMap { createKeyCommand(keyEntry: $0) } + keyCommands!.append(contentsOf: globalAuxilaryKeyCommands()) + + let specificFile = Bundle.main.path(forResource: type.rawValue, ofType: "plist")! + let specificEntries = NSArray(contentsOfFile: specificFile)! as! [[String: Any]] + keyCommands!.append(contentsOf: specificEntries.compactMap { createKeyCommand(keyEntry: $0) } ) - switch type { - case .global: - keyCommands?.append(contentsOf: globalAuxilaryKeyCommands()) - case .sidebar: - keyCommands?.append(contentsOf: sidebarAuxilaryKeyCommands()) - default: - break + if type == .sidebar { + keyCommands!.append(contentsOf: sidebarAuxilaryKeyCommands()) } } diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 3ea657a2a..c02d23594 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -13,11 +13,6 @@ class RootSplitViewController: UISplitViewController { var coordinator: SceneCoordinator! - private let keyboardManager = KeyboardManager(type: .global) - override var keyCommands: [UIKeyCommand]? { - return keyboardManager.keyCommands - } - // MARK: Keyboard Shortcuts @objc func scrollOrGoToNextUnread(_ sender: Any?) { From 8e01e8e45ae6faf950603a579069fe932724a37b Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Fri, 6 Sep 2019 09:13:09 -0400 Subject: [PATCH 20/24] Instantiate an RSAppMovementMonitor to track user movement of the application while running. Fixes #897. --- Mac/AppDelegate.swift | 1 + submodules/RSCore | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 5ccd8cae9..c5c12590e 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -63,6 +63,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, private var crashReportWindowController: CrashReportWindowController? // For testing only private let log = Log() private let appNewsURLString = "https://nnw.ranchero.com/feed.json" + private let appMovementMonitor = RSAppMovementMonitor() override init() { NSWindow.allowsAutomaticWindowTabbing = false diff --git a/submodules/RSCore b/submodules/RSCore index 89bcb8241..290b79dcd 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 89bcb8241afe59bb276c1e3bcfc83db6b4550c77 +Subproject commit 290b79dcd6156210dedddeb767164436ef9481e0 From 9f6203e839b4c67b211b80aefe1d2df40bde6734 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 6 Sep 2019 10:29:00 -0500 Subject: [PATCH 21/24] Improve launch performance and fix Search home page quick action --- .../MasterTimelineViewController.swift | 1 + iOS/SceneCoordinator.swift | 13 ++++++++++--- iOS/SceneDelegate.swift | 9 +++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 96d8f6e26..48cfcaeed 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -169,6 +169,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner func showSearchAll() { navigationItem.searchController?.isActive = true navigationItem.searchController?.searchBar.selectedScopeButtonIndex = 1 + navigationItem.searchController?.searchBar.becomeFirstResponder() } func focus() { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 27aaacbd6..ef7bdec59 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -327,7 +327,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { func showSearch() { selectFeed(nil) - masterTimelineViewController?.showSearchAll() + + masterTimelineViewController = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self) + masterTimelineViewController!.coordinator = self + navControllerForTimeline().pushViewController(masterTimelineViewController!, animated: false) + + DispatchQueue.main.asyncAfter(deadline: .now()) { + self.masterTimelineViewController!.showSearchAll() + } } // MARK: Notifications @@ -633,13 +640,13 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { if indexPath == nil { if rootSplitViewController.isCollapsed { if masterNavigationController.children.last is DetailViewController { - masterNavigationController.popViewController(animated: false) + masterNavigationController.popViewController(animated: !automated) } } else { let systemMessageViewController = UIStoryboard.main.instantiateController(ofType: SystemMessageViewController.self) installDetailController(systemMessageViewController, automated: automated) } - masterTimelineViewController?.updateArticleSelection(animate: true) + masterTimelineViewController?.updateArticleSelection(animate: !automated) return } diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index 4cdc78b5c..3c7503e7a 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -17,21 +17,22 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // UIWindowScene delegate func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + window = UIWindow(windowScene: scene as! UIWindowScene) window!.tintColor = AppAssets.netNewsWireBlueColor window!.rootViewController = coordinator.start() - window!.makeKeyAndVisible() if let shortcutItem = connectionOptions.shortcutItem { + window!.makeKeyAndVisible() handleShortcutItem(shortcutItem) return } if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity { - DispatchQueue.main.asyncAfter(deadline: .now()) { - self.coordinator.handle(userActivity) - } + self.coordinator.handle(userActivity) } + + window!.makeKeyAndVisible() } func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { From d0570d16a854e98cb72aa703a1cc3df830194478 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 6 Sep 2019 10:38:02 -0500 Subject: [PATCH 22/24] Prevent attempt to restore article selection when there are no articles --- iOS/MasterTimeline/MasterTimelineViewController.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 48cfcaeed..bfa04de25 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -156,6 +156,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } func updateArticleSelection(animate: Bool) { + guard !coordinator.articles.isEmpty else { return } + if let indexPath = coordinator.currentArticleIndexPath { if tableView.indexPathForSelectedRow != indexPath { tableView.selectRow(at: indexPath, animated: animate, scrollPosition: .middle) @@ -163,6 +165,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } else { tableView.selectRow(at: nil, animated: animate, scrollPosition: .none) } + updateUI() } From cc1c45095c00f054467f193dcb02961f49cb241c Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 6 Sep 2019 10:52:21 -0500 Subject: [PATCH 23/24] Make feeds become first responder on load so that the keyboard is ready to go on initial app launch --- iOS/MasterFeed/MasterFeedViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 1c2c10c2e..04fb25d19 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -56,6 +56,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { updateUI() applyChanges(animate: false) + becomeFirstResponder() } From cdbaeda97babf89ab00f5262cd13e6e816b4aafb Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 6 Sep 2019 10:57:37 -0500 Subject: [PATCH 24/24] Return to the Feeds scene when Add is executed using a keyboard shortcut key in another scene --- iOS/SceneCoordinator.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index ef7bdec59..3fc78544a 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -872,6 +872,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func showAdd(_ type: AddControllerType) { + selectFeed(nil) + let addViewController = UIStoryboard.add.instantiateInitialViewController() as! UINavigationController let containerController = addViewController.topViewController as! AddContainerViewController containerController.initialControllerType = type