diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 2a801f653..5bd756139 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 511D43D1231FA62800FB1562 /* SidebarKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */; }; 511D43D2231FA62C00FB1562 /* GlobalKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B641FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist */; }; 511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 511D43ED231FBDE800FB1562 /* LaunchScreenPad.storyboard */; }; + 511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D4410231FC02D00FB1562 /* KeyboardManager.swift */; }; 5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */; }; 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; }; 5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; @@ -32,6 +33,7 @@ 5144EA43227A380F00D19003 /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */; }; 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 */; }; 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 */; }; @@ -689,6 +691,7 @@ 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 = ""; }; 511D43EE231FBDE800FB1562 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreenPad.storyboard; sourceTree = ""; }; + 511D4410231FC02D00FB1562 /* KeyboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; 5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneCoordinator.swift; sourceTree = ""; }; 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailKeyboardDelegate.swift; sourceTree = ""; }; 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DetailKeyboardShortcuts.plist; sourceTree = ""; }; @@ -703,6 +706,7 @@ 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLWindowController.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; @@ -1781,6 +1785,8 @@ 840D617E2029031C009BC708 /* AppDelegate.swift */, 519E743422C663F900A78E47 /* SceneDelegate.swift */, 5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */, + 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */, + 511D4410231FC02D00FB1562 /* KeyboardManager.swift */, 51C45254226507D200C03939 /* AppAssets.swift */, 51C45255226507D200C03939 /* AppDefaults.swift */, 51E3EB3C229AB08300645299 /* ErrorHandler.swift */, @@ -2447,6 +2453,7 @@ 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */, + 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */, 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51C452852265093600C03939 /* AddFeedFolderPickerData.swift in Sources */, @@ -2518,6 +2525,7 @@ 51934CD023108953006127BE /* ActivityID.swift in Sources */, 51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */, 51C45259226508D300C03939 /* AppDefaults.swift in Sources */, + 511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */, 51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */, 51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */, 51934CCB230F599B006127BE /* ThemedNavigationController.swift in Sources */, diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 53374bdee..3b6af74f8 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -22,10 +22,15 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { var undoableCommands = [UndoableCommand]() weak var coordinator: SceneCoordinator! + lazy var keyboardManager = KeyboardManager(type: .sidebar, coordinator: coordinator) + override var keyCommands: [UIKeyCommand]? { + return keyboardManager.keyCommands + } + override var canBecomeFirstResponder: Bool { return true } - + override func viewDidLoad() { super.viewDidLoad() @@ -253,6 +258,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + becomeFirstResponder() coordinator.selectFeed(indexPath) } @@ -374,6 +380,12 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } } + // MARK: Keyboard shortcuts + + @objc func openInBrowser(_ sender: Any?) { + coordinator.showBrowserForCurrentFeed() + } + // MARK: API func updateFeedSelection() { @@ -651,31 +663,25 @@ private extension MasterFeedViewController { } func homePageAction(indexPath: IndexPath) -> UIAction? { - guard let node = coordinator.nodeFor(indexPath), - let feed = node.representedObject as? Feed, - let homePageURL = feed.homePageURL, - let url = URL(string: homePageURL) else { - return nil + guard coordinator.homePageURLForFeed(indexPath) != nil else { + return nil } let title = NSLocalizedString("Open Home Page", comment: "Open Home Page") - let action = UIAction(title: title, image: AppAssets.safariImage) { action in - UIApplication.shared.open(url, options: [:]) + let action = UIAction(title: title, image: AppAssets.safariImage) { [weak self] action in + self?.coordinator.showBrowserForFeed(indexPath) } return action } func homePageAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? { - guard let node = coordinator.nodeFor(indexPath), - let feed = node.representedObject as? Feed, - let homePageURL = feed.homePageURL, - let url = URL(string: homePageURL) else { - return nil + guard coordinator.homePageURLForFeed(indexPath) != nil else { + return nil } - + let title = NSLocalizedString("Open Home Page", comment: "Open Home Page") - let action = UIAlertAction(title: title, style: .default) { action in - UIApplication.shared.open(url, options: [:]) + let action = UIAlertAction(title: title, style: .default) { [weak self] action in + self?.coordinator.showBrowserForFeed(indexPath) completionHandler(true) } return action diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 062f6f103..68b0b4478 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -24,10 +24,15 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner weak var coordinator: SceneCoordinator! var undoableCommands = [UndoableCommand]() + lazy var keyboardManager = KeyboardManager(type: .timeline, coordinator: coordinator) + override var keyCommands: [UIKeyCommand]? { + return keyboardManager.keyCommands + } + override var canBecomeFirstResponder: Bool { return true } - + override func viewDidLoad() { super.viewDidLoad() @@ -261,6 +266,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + becomeFirstResponder() coordinator.selectArticle(indexPath, automated: false) } @@ -644,7 +650,7 @@ private extension MasterTimelineViewController { } let title = NSLocalizedString("Open in Browser", comment: "Open in Browser") let action = UIAction(title: title, image: AppAssets.safariImage) { [weak self] action in - self?.coordinator.showBrowser(for: indexPath) + self?.coordinator.showBrowserForArticle(indexPath) } return action } @@ -655,7 +661,7 @@ private extension MasterTimelineViewController { } let title = NSLocalizedString("Open in Browser", comment: "Open in Browser") let action = UIAlertAction(title: title, style: .default) { [weak self] action in - self?.coordinator.showBrowser(for: indexPath) + self?.coordinator.showBrowserForArticle(indexPath) completionHandler(true) } return action diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift new file mode 100644 index 000000000..fe63a7d28 --- /dev/null +++ b/iOS/RootSplitViewController.swift @@ -0,0 +1,21 @@ +// +// RootSplitViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 9/4/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class RootSplitViewController: UISplitViewController { + + var coordinator: SceneCoordinator! + + // MARK: Keyboard Shortcuts + + @objc func openInBrowser(_ sender: Any?) { + coordinator.showBrowserForCurrentArticle() + } + +} diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 60698332d..582cfd039 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -26,7 +26,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { private var activityManager = ActivityManager() - private var rootSplitViewController: UISplitViewController! + private var rootSplitViewController: RootSplitViewController! private var masterNavigationController: UINavigationController! private var masterFeedViewController: MasterFeedViewController! private var masterTimelineViewController: MasterTimelineViewController? @@ -222,7 +222,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func start() -> UIViewController { - rootSplitViewController = UISplitViewController.template() + rootSplitViewController = RootSplitViewController() + rootSplitViewController.coordinator = self + rootSplitViewController.preferredDisplayMode = .automatic + rootSplitViewController.viewControllers = [ThemedNavigationController.template()] rootSplitViewController.delegate = self masterNavigationController = (rootSplitViewController.viewControllers.first as! UINavigationController) @@ -707,7 +710,29 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { masterFeedViewController.present(addViewController, animated: true) } - func showBrowser(for indexPath: IndexPath) { + func homePageURLForFeed(_ indexPath: IndexPath) -> URL? { + guard let node = nodeFor(indexPath), + let feed = node.representedObject as? Feed, + let homePageURL = feed.homePageURL, + let url = URL(string: homePageURL) else { + return nil + } + return url + } + + func showBrowserForFeed(_ indexPath: IndexPath) { + if let url = homePageURLForFeed(indexPath) { + UIApplication.shared.open(url, options: [:]) + } + } + + func showBrowserForCurrentFeed() { + if let ip = currentMasterIndexPath, let url = homePageURLForFeed(ip) { + UIApplication.shared.open(url, options: [:]) + } + } + + func showBrowserForArticle(_ indexPath: IndexPath) { guard let preferredLink = articles[indexPath.row].preferredLink, let url = URL(string: preferredLink) else { return }