2019-02-15 06:47:08 +01:00
|
|
|
|
//
|
2019-02-15 06:58:45 +01:00
|
|
|
|
// TimelineContainerViewController.swift
|
2019-02-15 06:47:08 +01:00
|
|
|
|
// NetNewsWire
|
|
|
|
|
//
|
|
|
|
|
// Created by Brent Simmons on 2/14/19.
|
|
|
|
|
// Copyright © 2019 Ranchero Software. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
2019-02-18 06:43:51 +01:00
|
|
|
|
import AppKit
|
2020-03-01 00:50:13 +01:00
|
|
|
|
import Account
|
2019-02-18 06:43:51 +01:00
|
|
|
|
import Articles
|
|
|
|
|
|
|
|
|
|
protocol TimelineContainerViewControllerDelegate: class {
|
|
|
|
|
func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode)
|
2020-03-01 00:50:13 +01:00
|
|
|
|
func timelineRequestedWebFeedSelection(_: TimelineContainerViewController, webFeed: WebFeed)
|
2020-03-03 19:54:37 +01:00
|
|
|
|
func timelineInvalidatedRestorationState(_: TimelineContainerViewController)
|
|
|
|
|
|
2019-02-18 06:43:51 +01:00
|
|
|
|
}
|
2019-02-15 06:47:08 +01:00
|
|
|
|
|
2019-02-17 07:14:06 +01:00
|
|
|
|
final class TimelineContainerViewController: NSViewController {
|
|
|
|
|
|
2020-03-22 21:39:37 +01:00
|
|
|
|
@IBOutlet weak var viewOptionsPopUpButton: NSPopUpButton!
|
|
|
|
|
@IBOutlet weak var newestToOldestMenuItem: NSMenuItem!
|
|
|
|
|
@IBOutlet weak var oldestToNewestMenuItem: NSMenuItem!
|
|
|
|
|
@IBOutlet weak var groupByFeedMenuItem: NSMenuItem!
|
|
|
|
|
|
|
|
|
|
@IBOutlet weak var readFilteredButton: NSButton!
|
2019-02-17 07:14:06 +01:00
|
|
|
|
@IBOutlet var containerView: TimelineContainerView!
|
2019-02-18 06:43:51 +01:00
|
|
|
|
|
2019-02-18 21:38:14 +01:00
|
|
|
|
var currentTimelineViewController: TimelineViewController? {
|
|
|
|
|
didSet {
|
|
|
|
|
let view = currentTimelineViewController?.view
|
|
|
|
|
if containerView.contentView === view {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
containerView.contentView = view
|
2019-03-04 00:55:54 +01:00
|
|
|
|
view?.window?.recalculateKeyViewLoop()
|
2019-02-18 21:38:14 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-18 06:43:51 +01:00
|
|
|
|
weak var delegate: TimelineContainerViewControllerDelegate?
|
|
|
|
|
|
2019-11-24 11:29:15 +01:00
|
|
|
|
var isReadFiltered: Bool? {
|
|
|
|
|
guard let currentTimelineViewController = currentTimelineViewController, mode(for: currentTimelineViewController) == .regular else { return nil }
|
2019-11-22 18:47:03 +01:00
|
|
|
|
return regularTimelineViewController.isReadFiltered
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-28 07:53:27 +02:00
|
|
|
|
lazy var regularTimelineViewController = {
|
2019-02-17 07:14:06 +01:00
|
|
|
|
return TimelineViewController(delegate: self)
|
|
|
|
|
}()
|
2019-07-06 05:06:31 +02:00
|
|
|
|
private lazy var searchTimelineViewController: TimelineViewController = {
|
|
|
|
|
let viewController = TimelineViewController(delegate: self)
|
|
|
|
|
viewController.showsSearchResults = true
|
|
|
|
|
return viewController
|
2019-02-17 07:14:06 +01:00
|
|
|
|
}()
|
2019-02-15 06:47:08 +01:00
|
|
|
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
|
|
super.viewDidLoad()
|
2019-02-18 04:38:17 +01:00
|
|
|
|
setRepresentedObjects(nil, mode: .regular)
|
2019-02-25 07:39:57 +01:00
|
|
|
|
showTimeline(for: .regular)
|
2020-03-22 21:39:37 +01:00
|
|
|
|
updateViewOptionsPopUpButton()
|
|
|
|
|
|
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
|
2019-02-15 06:47:08 +01:00
|
|
|
|
}
|
2020-03-22 21:39:37 +01:00
|
|
|
|
|
|
|
|
|
// MARK: - Notifications
|
|
|
|
|
|
|
|
|
|
@objc func userDefaultsDidChange(_ note: Notification) {
|
|
|
|
|
updateViewOptionsPopUpButton()
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-17 07:14:06 +01:00
|
|
|
|
// MARK: - API
|
|
|
|
|
|
2019-02-18 04:38:17 +01:00
|
|
|
|
func setRepresentedObjects(_ objects: [AnyObject]?, mode: TimelineSourceMode) {
|
|
|
|
|
timelineViewController(for: mode).representedObjects = objects
|
2020-03-22 21:39:37 +01:00
|
|
|
|
updateReadFilterButton()
|
2019-02-17 07:14:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-25 07:39:57 +01:00
|
|
|
|
func showTimeline(for mode: TimelineSourceMode) {
|
2019-02-18 21:38:14 +01:00
|
|
|
|
currentTimelineViewController = timelineViewController(for: mode)
|
2019-07-29 01:00:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func regularTimelineViewControllerHasRepresentedObjects(_ representedObjects: [AnyObject]?) -> Bool {
|
|
|
|
|
// Use this to find out if the regular timeline view already has the specified representedObjects.
|
|
|
|
|
// This is used in determining whether a search should end.
|
|
|
|
|
// The sidebar may think that the selection has changed, and therefore search should end —
|
|
|
|
|
// but it could be that the regular timeline already has these representedObjects,
|
|
|
|
|
// and therefore the selection hasn’t actually changed,
|
|
|
|
|
// and therefore search shouldn’t end.
|
|
|
|
|
// https://github.com/brentsimmons/NetNewsWire/issues/791
|
|
|
|
|
if representedObjects == nil && regularTimelineViewController.representedObjects == nil {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
guard let currentObjects = regularTimelineViewController.representedObjects, let representedObjects = representedObjects else {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if currentObjects.count != representedObjects.count {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for object in representedObjects {
|
|
|
|
|
guard let _ = currentObjects.firstIndex(where: { $0 === object } ) else {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
2019-02-17 07:14:06 +01:00
|
|
|
|
}
|
2019-11-22 18:47:03 +01:00
|
|
|
|
|
2020-03-16 00:02:55 +01:00
|
|
|
|
func cleanUp() {
|
|
|
|
|
regularTimelineViewController.cleanUp()
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-22 18:47:03 +01:00
|
|
|
|
func toggleReadFilter() {
|
|
|
|
|
regularTimelineViewController.toggleReadFilter()
|
2020-03-22 21:39:37 +01:00
|
|
|
|
updateReadFilterButton()
|
2019-11-22 18:47:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 19:54:37 +01:00
|
|
|
|
// MARK: State Restoration
|
|
|
|
|
|
2020-03-06 00:42:54 +01:00
|
|
|
|
func saveState(to state: inout [AnyHashable : Any]) {
|
|
|
|
|
regularTimelineViewController.saveState(to: &state)
|
2020-03-03 19:54:37 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-06 00:42:54 +01:00
|
|
|
|
func restoreState(from state: [AnyHashable : Any]) {
|
|
|
|
|
regularTimelineViewController.restoreState(from: state)
|
2020-03-22 21:39:37 +01:00
|
|
|
|
updateReadFilterButton()
|
2020-03-03 19:54:37 +01:00
|
|
|
|
}
|
2019-02-17 07:14:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension TimelineContainerViewController: TimelineDelegate {
|
|
|
|
|
|
2019-02-18 06:43:51 +01:00
|
|
|
|
func timelineSelectionDidChange(_ timelineViewController: TimelineViewController, selectedArticles: [Article]?) {
|
|
|
|
|
delegate?.timelineSelectionDidChange(self, articles: selectedArticles, mode: mode(for: timelineViewController))
|
2019-02-17 07:14:06 +01:00
|
|
|
|
}
|
2020-03-01 00:50:13 +01:00
|
|
|
|
|
|
|
|
|
func timelineRequestedWebFeedSelection(_: TimelineViewController, webFeed: WebFeed) {
|
|
|
|
|
delegate?.timelineRequestedWebFeedSelection(self, webFeed: webFeed)
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 19:54:37 +01:00
|
|
|
|
func timelineInvalidatedRestorationState(_: TimelineViewController) {
|
|
|
|
|
delegate?.timelineInvalidatedRestorationState(self)
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-17 07:14:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private extension TimelineContainerViewController {
|
|
|
|
|
|
|
|
|
|
func timelineViewController(for mode: TimelineSourceMode) -> TimelineViewController {
|
|
|
|
|
switch mode {
|
|
|
|
|
case .regular:
|
|
|
|
|
return regularTimelineViewController
|
|
|
|
|
case .search:
|
|
|
|
|
return searchTimelineViewController
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-18 06:43:51 +01:00
|
|
|
|
|
|
|
|
|
func mode(for timelineViewController: TimelineViewController) -> TimelineSourceMode {
|
|
|
|
|
if timelineViewController === regularTimelineViewController {
|
|
|
|
|
return .regular
|
|
|
|
|
}
|
|
|
|
|
else if timelineViewController === searchTimelineViewController {
|
|
|
|
|
return .search
|
|
|
|
|
}
|
|
|
|
|
assertionFailure("Expected timelineViewController to match either regular or search timelineViewController, but it doesn’t.")
|
|
|
|
|
return .regular // Should never get here.
|
|
|
|
|
}
|
2020-03-22 21:39:37 +01:00
|
|
|
|
|
|
|
|
|
func updateViewOptionsPopUpButton() {
|
|
|
|
|
let localizedTitle = NSLocalizedString("Sort %@", comment: "Sort")
|
|
|
|
|
|
|
|
|
|
if AppDefaults.timelineSortDirection == .orderedAscending {
|
|
|
|
|
newestToOldestMenuItem.state = .off
|
|
|
|
|
oldestToNewestMenuItem.state = .on
|
|
|
|
|
let title = NSString.localizedStringWithFormat(localizedTitle as NSString, oldestToNewestMenuItem.title) as String
|
|
|
|
|
viewOptionsPopUpButton.setTitle(title)
|
|
|
|
|
} else {
|
|
|
|
|
newestToOldestMenuItem.state = .on
|
|
|
|
|
oldestToNewestMenuItem.state = .off
|
|
|
|
|
let title = NSString.localizedStringWithFormat(localizedTitle as NSString, newestToOldestMenuItem.title) as String
|
|
|
|
|
viewOptionsPopUpButton.setTitle(title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if AppDefaults.timelineGroupByFeed == true {
|
|
|
|
|
groupByFeedMenuItem.state = .on
|
|
|
|
|
} else {
|
|
|
|
|
groupByFeedMenuItem.state = .off
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func updateReadFilterButton() {
|
|
|
|
|
guard let isReadFiltered = regularTimelineViewController.isReadFiltered else {
|
|
|
|
|
readFilteredButton.isHidden = true
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
readFilteredButton.isHidden = false
|
|
|
|
|
|
|
|
|
|
if isReadFiltered {
|
|
|
|
|
readFilteredButton.image = AppAssets.filterActive
|
|
|
|
|
} else {
|
|
|
|
|
readFilteredButton.image = AppAssets.filterInactive
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-15 06:47:08 +01:00
|
|
|
|
}
|