Merge branch 'master' of https://github.com/brentsimmons/NetNewsWire
This commit is contained in:
commit
80ef4e18f0
|
@ -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
|
||||
|
|
|
@ -124,7 +124,7 @@ pre {
|
|||
}
|
||||
img, figure, video, iframe {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
height: auto !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = "<group>"; };
|
||||
510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeedbinAccountView.swift; sourceTree = "<group>"; };
|
||||
510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountLabelView.swift; sourceTree = "<group>"; };
|
||||
5110AB7722B7BD6200A94F76 /* AddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddView.swift; sourceTree = "<group>"; };
|
||||
51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = "<group>"; };
|
||||
51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = "<group>"; };
|
||||
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = "<group>"; };
|
||||
|
@ -707,6 +706,7 @@
|
|||
5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedbinWindowController.swift; sourceTree = "<group>"; };
|
||||
5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = "<group>"; };
|
||||
514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = "<group>"; };
|
||||
514B7D1E23219F3C00BAC947 /* AddControllerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddControllerType.swift; sourceTree = "<group>"; };
|
||||
51543684228F6753005E1CDF /* DetailAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailAccountViewController.swift; sourceTree = "<group>"; };
|
||||
515436872291D75D005E1CDF /* AddLocalAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddLocalAccountViewController.swift; sourceTree = "<group>"; };
|
||||
515436892291FED9005E1CDF /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -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 = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
<string>goToPreviousUnread:</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>title</key>
|
||||
<string>Go to Previous Unread</string>
|
||||
<key>key</key>
|
||||
<string>[uparrow]</string>
|
||||
<key>shiftModifier</key>
|
||||
|
@ -29,16 +27,12 @@
|
|||
<string>goToPreviousUnread:</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>title</key>
|
||||
<string>Next Unread</string>
|
||||
<key>key</key>
|
||||
<string>+</string>
|
||||
<key>action</key>
|
||||
<string>nextUnread:</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>title</key>
|
||||
<string>Next Unread</string>
|
||||
<key>key</key>
|
||||
<string>+</string>
|
||||
<key>shiftModifier</key>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</dict>
|
||||
<dict>
|
||||
<key>title</key>
|
||||
<string>Collapse Selected Rows</string>
|
||||
<string>Collapse Selected Row</string>
|
||||
<key>key</key>
|
||||
<string>,</string>
|
||||
<key>action</key>
|
||||
|
@ -18,7 +18,7 @@
|
|||
</dict>
|
||||
<dict>
|
||||
<key>title</key>
|
||||
<string>Expand Selected Rows</string>
|
||||
<string>Expand Selected Row</string>
|
||||
<key>key</key>
|
||||
<string>.</string>
|
||||
<key>action</key>
|
||||
|
|
|
@ -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) {
|
|
@ -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
|
||||
}
|
|
@ -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)
|
|
@ -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 {
|
|
@ -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
|
|
@ -26,15 +26,11 @@ 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
|
||||
}
|
||||
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
deinit {
|
||||
webView.removeFromSuperview()
|
||||
DetailViewControllerWebViewProvider.shared.enqueueWebView(webView)
|
||||
|
@ -155,7 +151,7 @@ class DetailViewController: UIViewController {
|
|||
}
|
||||
|
||||
@IBAction func toggleStar(_ sender: Any) {
|
||||
coordinator.toggleStarForCurrentArticle()
|
||||
coordinator.toggleStarredForCurrentArticle()
|
||||
}
|
||||
|
||||
@IBAction func openBrowser(_ sender: Any) {
|
||||
|
@ -188,7 +184,28 @@ 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()
|
||||
return fullScroll < final ? fullScroll : 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 {
|
||||
|
||||
|
|
|
@ -17,36 +17,27 @@ 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)
|
||||
init(type: KeyboardType) {
|
||||
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) } )
|
||||
|
||||
if type == .sidebar {
|
||||
keyCommands!.append(contentsOf: sidebarAuxilaryKeyCommands())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if type == .sidebar {
|
||||
globalCommands.append(contentsOf: sidebarAuxilaryKeyCommands())
|
||||
}
|
||||
|
||||
keyCommands = globalCommands
|
||||
}
|
||||
|
||||
func createKeyCommand(keyEntry: [String: Any]) -> UIKeyCommand? {
|
||||
guard let input = createKeyCommandInput(keyEntry: keyEntry) else { return nil }
|
||||
let modifiers = createKeyModifierFlags(keyEntry: keyEntry)
|
||||
|
@ -69,7 +60,7 @@ private extension KeyboardManager {
|
|||
|
||||
switch(key) {
|
||||
case "[space]":
|
||||
return " "
|
||||
return "\u{0020}"
|
||||
case "[uparrow]":
|
||||
return UIKeyCommand.inputUpArrow
|
||||
case "[downarrow]":
|
||||
|
@ -116,6 +107,48 @@ 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]))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func sidebarAuxilaryKeyCommands() -> [UIKeyCommand] {
|
||||
var keys = [UIKeyCommand]()
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
|
||||
updateUI()
|
||||
applyChanges(animate: false)
|
||||
becomeFirstResponder()
|
||||
|
||||
}
|
||||
|
||||
|
@ -259,7 +260,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 {
|
||||
|
@ -347,7 +348,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
}
|
||||
|
||||
@IBAction func add(_ sender: UIBarButtonItem) {
|
||||
coordinator.showAdd()
|
||||
coordinator.showAdd(.feed)
|
||||
}
|
||||
|
||||
@objc func toggleSectionHeader(_ sender: UITapGestureRecognizer) {
|
||||
|
@ -361,11 +362,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)
|
||||
}
|
||||
|
||||
|
@ -398,6 +399,36 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
coordinator.showBrowserForCurrentFeed()
|
||||
}
|
||||
|
||||
@objc override func delete(_ sender: Any?) {
|
||||
if let indexPath = coordinator.currentFeedIndexPath {
|
||||
delete(indexPath: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func expandSelectedRows(_ sender: Any?) {
|
||||
if let indexPath = coordinator.currentFeedIndexPath {
|
||||
coordinator.expandFolder(indexPath)
|
||||
self.applyChanges(animate: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func collapseSelectedRows(_ sender: Any?) {
|
||||
if let indexPath = coordinator.currentFeedIndexPath {
|
||||
coordinator.collapseFolder(indexPath)
|
||||
self.applyChanges(animate: true)
|
||||
}
|
||||
}
|
||||
|
||||
@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() {
|
||||
|
@ -421,24 +452,42 @@ 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.expandSection(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 {
|
||||
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
|
||||
}
|
||||
|
||||
coordinator.expand(indexPath)
|
||||
coordinator.expandFolder(indexPath)
|
||||
reloadNode(parent)
|
||||
|
||||
self.applyChanges(animate: true) { [weak self] in
|
||||
|
@ -481,12 +530,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 +551,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<Int, Node> {
|
||||
return MasterFeedDataSource(coordinator: coordinator, errorHandler: ErrorHandler.present(self), tableView: tableView, cellProvider: { [weak self] tableView, indexPath, node in
|
||||
|
@ -613,7 +645,7 @@ private extension MasterFeedViewController {
|
|||
guard let indexPath = tableView.indexPath(for: cell) else {
|
||||
return
|
||||
}
|
||||
coordinator.expand(indexPath)
|
||||
coordinator.expandFolder(indexPath)
|
||||
self.applyChanges(animate: true)
|
||||
}
|
||||
|
||||
|
@ -621,7 +653,7 @@ private extension MasterFeedViewController {
|
|||
guard let indexPath = tableView.indexPath(for: cell) else {
|
||||
return
|
||||
}
|
||||
coordinator.collapse(indexPath)
|
||||
coordinator.collapseFolder(indexPath)
|
||||
self.applyChanges(animate: true)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -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,12 +165,14 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||
} else {
|
||||
tableView.selectRow(at: nil, animated: animate, scrollPosition: .none)
|
||||
}
|
||||
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func showSearchAll() {
|
||||
navigationItem.searchController?.isActive = true
|
||||
navigationItem.searchController?.searchBar.selectedScopeButtonIndex = 1
|
||||
navigationItem.searchController?.searchBar.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func focus() {
|
||||
|
|
|
@ -122,7 +122,7 @@ pre {
|
|||
}
|
||||
img, figure, video, iframe {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
height: auto !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import Account
|
||||
|
||||
class RootSplitViewController: UISplitViewController {
|
||||
|
||||
|
@ -14,8 +15,82 @@ class RootSplitViewController: UISplitViewController {
|
|||
|
||||
// MARK: Keyboard Shortcuts
|
||||
|
||||
@objc func scrollOrGoToNextUnread(_ sender: Any?) {
|
||||
coordinator.scrollOrGoToNextUnread()
|
||||
}
|
||||
|
||||
@objc func goToPreviousUnread(_ sender: Any?) {
|
||||
coordinator.selectPrevUnread()
|
||||
}
|
||||
|
||||
@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?) {
|
||||
coordinator.selectPrevFeed()
|
||||
}
|
||||
|
||||
@objc func goToNextSubscription(_ sender: Any?) {
|
||||
coordinator.selectNextFeed()
|
||||
}
|
||||
|
||||
@objc func openInBrowser(_ sender: Any?) {
|
||||
coordinator.showBrowserForCurrentArticle()
|
||||
}
|
||||
|
||||
@objc func addNewFeed(_ sender: Any?) {
|
||||
coordinator.showAdd(.feed)
|
||||
}
|
||||
|
||||
@objc func addNewFolder(_ sender: Any?) {
|
||||
coordinator.showAdd(.folder)
|
||||
}
|
||||
|
||||
@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()
|
||||
}
|
||||
|
||||
@objc func toggleRead(_ sender: Any?) {
|
||||
coordinator.toggleReadForCurrentArticle()
|
||||
}
|
||||
|
||||
@objc func toggleStarred(_ sender: Any?) {
|
||||
coordinator.toggleStarredForCurrentArticle()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
@ -326,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
|
||||
|
@ -436,10 +444,11 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
return 0
|
||||
}
|
||||
|
||||
func expand(section: Int) {
|
||||
guard let expandNode = treeController.rootNode.childAtIndex(section) else {
|
||||
func expandSection(_ section: Int) {
|
||||
guard let expandNode = treeController.rootNode.childAtIndex(section), !expandedNodes.contains(expandNode) else {
|
||||
return
|
||||
}
|
||||
|
||||
expandedNodes.append(expandNode)
|
||||
|
||||
animatingChanges = true
|
||||
|
@ -463,8 +472,23 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
animatingChanges = false
|
||||
}
|
||||
|
||||
func expand(_ indexPath: IndexPath) {
|
||||
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 }
|
||||
expandedNodes.append(expandNode)
|
||||
|
||||
animatingChanges = true
|
||||
|
@ -479,13 +503,13 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
animatingChanges = false
|
||||
}
|
||||
|
||||
func collapse(section: Int) {
|
||||
animatingChanges = true
|
||||
|
||||
guard let collapseNode = treeController.rootNode.childAtIndex(section) else {
|
||||
func collapseSection(_ section: Int) {
|
||||
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)
|
||||
}
|
||||
|
@ -495,10 +519,21 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
animatingChanges = false
|
||||
}
|
||||
|
||||
func collapse(_ indexPath: IndexPath) {
|
||||
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
|
||||
|
||||
let collapseNode = shadowTable[indexPath.section][indexPath.row]
|
||||
guard expandedNodes.contains(collapseNode) else { return }
|
||||
if let removeNode = expandedNodes.firstIndex(of: collapseNode) {
|
||||
expandedNodes.remove(at: removeNode)
|
||||
}
|
||||
|
@ -540,24 +575,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() {
|
||||
|
@ -571,6 +610,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
|
||||
|
@ -581,18 +638,22 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
}
|
||||
|
||||
if indexPath == nil {
|
||||
if !rootSplitViewController.isCollapsed {
|
||||
if rootSplitViewController.isCollapsed {
|
||||
if masterNavigationController.children.last is DetailViewController {
|
||||
masterNavigationController.popViewController(animated: !automated)
|
||||
}
|
||||
} else {
|
||||
let systemMessageViewController = UIStoryboard.main.instantiateController(ofType: SystemMessageViewController.self)
|
||||
installDetailController(systemMessageViewController)
|
||||
installDetailController(systemMessageViewController, automated: automated)
|
||||
}
|
||||
masterTimelineViewController?.updateArticleSelection(animate: true)
|
||||
masterTimelineViewController?.updateArticleSelection(animate: !automated)
|
||||
return
|
||||
}
|
||||
|
||||
if detailViewController == nil {
|
||||
let detailViewController = UIStoryboard.main.instantiateController(ofType: DetailViewController.self)
|
||||
detailViewController.coordinator = self
|
||||
installDetailController(detailViewController)
|
||||
installDetailController(detailViewController, automated: automated)
|
||||
}
|
||||
|
||||
// Automatically hide the overlay
|
||||
|
@ -604,7 +665,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
}
|
||||
|
||||
if automated {
|
||||
masterTimelineViewController?.updateArticleSelection(animate: true)
|
||||
masterTimelineViewController?.updateArticleSelection(animate: false)
|
||||
}
|
||||
|
||||
detailViewController?.updateArticleSelection()
|
||||
|
@ -672,6 +733,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
|
||||
|
@ -692,6 +769,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
|
||||
|
@ -713,6 +798,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 }
|
||||
|
@ -722,6 +812,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)
|
||||
|
@ -737,7 +839,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)
|
||||
}
|
||||
|
@ -753,7 +855,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
}
|
||||
|
||||
func discloseFeed(_ feed: Feed, completion: (() -> Void)? = nil) {
|
||||
masterNavigationController.popViewController(animated: true)
|
||||
masterFeedViewController.discloseFeed(feed) {
|
||||
completion?()
|
||||
}
|
||||
|
@ -770,8 +871,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
// self.present(settings, animated: true)
|
||||
}
|
||||
|
||||
func showAdd() {
|
||||
let addViewController = UIStoryboard.add.instantiateInitialViewController()!
|
||||
func showAdd(_ type: AddControllerType) {
|
||||
selectFeed(nil)
|
||||
|
||||
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)
|
||||
|
@ -819,10 +924,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
}
|
||||
|
||||
func navigateToTimeline() {
|
||||
masterTimelineViewController?.focus()
|
||||
if currentArticleIndexPath == nil {
|
||||
selectArticle(IndexPath(row: 0, section: 0))
|
||||
}
|
||||
masterTimelineViewController?.focus()
|
||||
}
|
||||
|
||||
func navigateToDetail() {
|
||||
|
@ -984,11 +1089,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
|
||||
|
@ -1001,10 +1208,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
|
||||
|
@ -1024,10 +1231,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 = {
|
||||
|
@ -1054,7 +1264,15 @@ private extension SceneCoordinator {
|
|||
|
||||
for i in indexPath.section..<shadowTable.count {
|
||||
|
||||
for j in indexPath.row..<shadowTable[indexPath.section].count {
|
||||
let startingRow: Int = {
|
||||
if indexPath.section == i {
|
||||
return indexPath.row
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}()
|
||||
|
||||
for j in startingRow..<shadowTable[indexPath.section].count {
|
||||
|
||||
let nextIndexPath = IndexPath(row: j, section: i)
|
||||
guard let node = nodeFor(nextIndexPath), let unreadCountProvider = node.representedObject as? UnreadCountProvider else {
|
||||
|
@ -1262,7 +1480,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)
|
||||
|
||||
|
@ -1270,7 +1488,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)
|
||||
|
|
|
@ -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) {
|
||||
|
@ -66,7 +67,7 @@ private extension SceneDelegate {
|
|||
case "com.ranchero.NetNewsWire.ShowSearch":
|
||||
coordinator.showSearch()
|
||||
case "com.ranchero.NetNewsWire.ShowAdd":
|
||||
coordinator.showAdd()
|
||||
coordinator.showAdd(.feed)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b8656655f68f207bf9d14e9fda2c928c1bcbe0cf
|
||||
Subproject commit 50cf102acd0592ec3bff2446f19386b6593e1ff8
|
Loading…
Reference in New Issue