From fcf2937394a12fba75efc9ffda52590488d02755 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 29 Jun 2019 13:35:12 -0500 Subject: [PATCH] Rename NavStateController to AppCoordinator because we are redesigning it to be the Coordinator pattern --- NetNewsWire.xcodeproj/project.pbxproj | 8 +- iOS/AppCoordinator.swift | 663 ++++++++++++++++++ iOS/Detail/DetailViewController.swift | 36 +- iOS/MasterFeed/MasterFeedViewController.swift | 100 +-- .../MasterTimelineViewController.swift | 63 +- iOS/SceneDelegate.swift | 26 +- 6 files changed, 772 insertions(+), 124 deletions(-) create mode 100644 iOS/AppCoordinator.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index bf238bdb4..3285bfd98 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ 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 */; }; - 5126EE97226CB48A00C22AFC /* NavigationStateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5126EE96226CB48A00C22AFC /* NavigationStateController.swift */; }; + 5126EE97226CB48A00C22AFC /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5126EE96226CB48A00C22AFC /* AppCoordinator.swift */; }; 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; }; 5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; @@ -666,7 +666,7 @@ 51121AA12265430A00BC0EC1 /* NetNewsWire_iOS_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOS_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 = ""; }; - 5126EE96226CB48A00C22AFC /* NavigationStateController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStateController.swift; sourceTree = ""; }; + 5126EE96226CB48A00C22AFC /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.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 = ""; }; 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedTableViewSectionHeader.swift; sourceTree = ""; }; @@ -1709,10 +1709,10 @@ 84C9FC9F2262A1B300D921D6 /* Main.storyboard */, 840D617E2029031C009BC708 /* AppDelegate.swift */, 519E743422C663F900A78E47 /* SceneDelegate.swift */, + 5126EE96226CB48A00C22AFC /* AppCoordinator.swift */, 51C45254226507D200C03939 /* AppAssets.swift */, 51C45255226507D200C03939 /* AppDefaults.swift */, 51E3EB3C229AB08300645299 /* ErrorHandler.swift */, - 5126EE96226CB48A00C22AFC /* NavigationStateController.swift */, 51C4525D226508F600C03939 /* MasterFeed */, 51C4526D2265091600C03939 /* MasterTimeline */, 51C4527D2265092C00C03939 /* Detail */, @@ -2346,7 +2346,7 @@ 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51C452852265093600C03939 /* AddFeedFolderPickerData.swift in Sources */, 51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */, - 5126EE97226CB48A00C22AFC /* NavigationStateController.swift in Sources */, + 5126EE97226CB48A00C22AFC /* AppCoordinator.swift in Sources */, 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, 5183CCEF227125970010922C /* SettingsViewController.swift in Sources */, diff --git a/iOS/AppCoordinator.swift b/iOS/AppCoordinator.swift new file mode 100644 index 000000000..958d0a393 --- /dev/null +++ b/iOS/AppCoordinator.swift @@ -0,0 +1,663 @@ +// +// NavigationModelController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/21/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import Account +import Articles +import RSCore +import RSTree + +public extension Notification.Name { + static let MasterSelectionDidChange = Notification.Name(rawValue: "MasterSelectionDidChange") + static let BackingStoresDidRebuild = Notification.Name(rawValue: "BackingStoresDidRebuild") + static let ArticlesReinitialized = Notification.Name(rawValue: "ArticlesReinitialized") + static let ArticleDataDidChange = Notification.Name(rawValue: "ArticleDataDidChange") + static let ArticlesDidChange = Notification.Name(rawValue: "ArticlesDidChange") + static let ArticleSelectionDidChange = Notification.Name(rawValue: "ArticleSelectionDidChange") +} + +class AppCoordinator { + + static let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5) + + private var articleRowMap = [String: Int]() // articleID: rowIndex + + private var animatingChanges = false + private var expandedNodes = [Node]() + private var shadowTable = [[Node]]() + + private var sortDirection = AppDefaults.timelineSortDirection { + didSet { + if sortDirection != oldValue { + sortDirectionDidChange() + } + } + } + + private let treeControllerDelegate = FeedTreeControllerDelegate() + lazy var treeController: TreeController = { + return TreeController(delegate: treeControllerDelegate) + }() + + var rootNode: Node { + return treeController.rootNode + } + + var numberOfSections: Int { + return shadowTable.count + } + + var currentMasterIndexPath: IndexPath? { + didSet { + guard let ip = currentMasterIndexPath, let node = nodeFor(ip) else { + assertionFailure() + return + } + if let fetcher = node.representedObject as? ArticleFetcher { + timelineFetcher = fetcher + } + NotificationCenter.default.post(name: .MasterSelectionDidChange, object: self, userInfo: nil) + } + } + + var timelineName: String? { + return (timelineFetcher as? DisplayNameProvider)?.nameForDisplay + } + + var timelineFetcher: ArticleFetcher? { + didSet { + currentArticleIndexPath = nil + if timelineFetcher is Feed { + showFeedNames = false + } else { + showFeedNames = true + } + fetchArticles() + NotificationCenter.default.post(name: .ArticlesReinitialized, object: self, userInfo: nil) + } + } + + + var showFeedNames = false + var showAvatars = false + + var isPrevArticleAvailable: Bool { + guard let indexPath = currentArticleIndexPath else { + return false + } + return indexPath.row > 0 + } + + var isNextArticleAvailable: Bool { + guard let indexPath = currentArticleIndexPath else { + return false + } + return indexPath.row + 1 < articles.count + } + + var prevArticleIndexPath: IndexPath? { + guard let indexPath = currentArticleIndexPath else { + return nil + } + return IndexPath(row: indexPath.row - 1, section: indexPath.section) + } + + var nextArticleIndexPath: IndexPath? { + guard let indexPath = currentArticleIndexPath else { + return nil + } + return IndexPath(row: indexPath.row + 1, section: indexPath.section) + } + + var firstUnreadArticleIndexPath: IndexPath? { + for (row, article) in articles.enumerated() { + if !article.status.read { + return IndexPath(row: row, section: 0) + } + } + return nil + } + + var currentArticle: Article? { + if let indexPath = currentArticleIndexPath { + return articles[indexPath.row] + } + return nil + } + + var currentArticleIndexPath: IndexPath? { + didSet { + if currentArticleIndexPath != oldValue { + NotificationCenter.default.post(name: .ArticleSelectionDidChange, object: self, userInfo: nil) + } + } + } + + var articles = ArticleArray() { + didSet { + if articles == oldValue { + return + } + if articles.representSameArticlesInSameOrder(as: oldValue) { + articleRowMap = [String: Int]() + NotificationCenter.default.post(name: .ArticleDataDidChange, object: self, userInfo: nil) + return + } + updateShowAvatars() + articleRowMap = [String: Int]() + NotificationCenter.default.post(name: .ArticlesDidChange, object: self, userInfo: nil) + } + } + + var isTimelineUnreadAvailable: Bool { + if let unreadProvider = timelineFetcher as? UnreadCountProvider { + return unreadProvider.unreadCount > 0 + } + return false + } + + var isAnyUnreadAvailable: Bool { + return appDelegate.unreadCount > 0 + } + + init() { + + for section in treeController.rootNode.childNodes { + expandedNodes.append(section) + shadowTable.append([Node]()) + } + + rebuildShadowTable() + + NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange(_:)), name: .AccountStateDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChange, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil) + + } + + // MARK: Notifications + + @objc func containerChildrenDidChange(_ note: Notification) { + rebuildBackingStores() + } + + @objc func batchUpdateDidPerform(_ notification: Notification) { + rebuildBackingStores() + } + + @objc func displayNameDidChange(_ note: Notification) { + rebuildBackingStores() + } + + @objc func accountStateDidChange(_ note: Notification) { + rebuildBackingStores() + } + + @objc func accountsDidChange(_ note: Notification) { + rebuildBackingStores() + } + + @objc func userDefaultsDidChange(_ note: Notification) { + self.sortDirection = AppDefaults.timelineSortDirection + } + + @objc func accountDidDownloadArticles(_ note: Notification) { + + guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set else { + return + } + + let shouldFetchAndMergeArticles = timelineFetcherContainsAnyFeed(feeds) || timelineFetcherContainsAnyPseudoFeed() + if shouldFetchAndMergeArticles { + queueFetchAndMergeArticles() + } + + } + + // MARK: API + + func beginUpdates() { + animatingChanges = true + } + + func endUpdates() { + animatingChanges = false + } + + func rowsInSection(_ section: Int) -> Int { + return shadowTable[section].count + } + + func rebuildShadowTable() { + + shadowTable = [[Node]]() + + for i in 0.. Bool { + return expandedNodes.contains(node) + } + + func nodeFor(_ indexPath: IndexPath) -> Node? { + guard indexPath.section < shadowTable.count || indexPath.row < shadowTable[indexPath.section].count else { + return nil + } + return shadowTable[indexPath.section][indexPath.row] + } + + func indexPathFor(_ node: Node) -> IndexPath? { + for i in 0.. ()) { + + guard let expandNode = treeController.rootNode.childAtIndex(section) else { + return + } + expandedNodes.append(expandNode) + + animatingChanges = true + + var indexPathsToInsert = [IndexPath]() + var i = 0 + + func addNode(_ node: Node) { + indexPathsToInsert.append(IndexPath(row: i, section: section)) + shadowTable[section].insert(node, at: i) + i = i + 1 + } + + for child in expandNode.childNodes { + addNode(child) + if expandedNodes.contains(child) { + for gChild in child.childNodes { + addNode(gChild) + } + } + } + + completion(indexPathsToInsert) + + animatingChanges = false + + } + + func expand(_ indexPath: IndexPath, completion: ([IndexPath]) -> ()) { + + let expandNode = shadowTable[indexPath.section][indexPath.row] + expandedNodes.append(expandNode) + + animatingChanges = true + + var indexPathsToInsert = [IndexPath]() + for i in 0.. ()) { + + animatingChanges = true + + guard let collapseNode = treeController.rootNode.childAtIndex(section) else { + return + } + + if let removeNode = expandedNodes.firstIndex(of: collapseNode) { + expandedNodes.remove(at: removeNode) + } + + var indexPathsToRemove = [IndexPath]() + for i in 0.. ()) { + + animatingChanges = true + + let collapseNode = shadowTable[indexPath.section][indexPath.row] + if let removeNode = expandedNodes.firstIndex(of: collapseNode) { + expandedNodes.remove(at: removeNode) + } + + var indexPathsToRemove = [IndexPath]() + + for child in collapseNode.childNodes { + if let index = shadowTable[indexPath.section].firstIndex(of: child) { + indexPathsToRemove.append(IndexPath(row: index, section: indexPath.section)) + } + } + + for child in collapseNode.childNodes { + if let index = shadowTable[indexPath.section].firstIndex(of: child) { + shadowTable[indexPath.section].remove(at: index) + } + } + + completion(indexPathsToRemove) + + animatingChanges = false + + } + + func indexesForArticleIDs(_ articleIDs: Set) -> IndexSet { + + var indexes = IndexSet() + + articleIDs.forEach { (articleID) in + guard let oneIndex = row(for: articleID) else { + return + } + if oneIndex != NSNotFound { + indexes.insert(oneIndex) + } + } + + return indexes + } + + func selectNextUnread() { + + // 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 selectNextUnreadArticleInTimeline() { + return + } + + selectNextUnreadFeedFetcher() + selectNextUnreadArticleInTimeline() + + } + +} + +// MARK: UISplitViewControllerDelegate + +extension AppCoordinator: UISplitViewControllerDelegate { + + func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { + if currentArticle == nil { + // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded. + return true + } + return false + } + +} + +// MARK: Private + +private extension AppCoordinator { + + func rebuildBackingStores() { + if !animatingChanges && !BatchUpdate.shared.isPerforming { + treeController.rebuild() + rebuildShadowTable() + NotificationCenter.default.post(name: .BackingStoresDidRebuild, object: self, userInfo: nil) + } + } + + func updateShowAvatars() { + + if showFeedNames { + self.showAvatars = true + return + } + + for article in articles { + if let authors = article.authors { + for author in authors { + if author.avatarURL != nil { + self.showAvatars = true + return + } + } + } + } + + self.showAvatars = false + } + + // MARK: Select Next Unread + + @discardableResult + func selectNextUnreadArticleInTimeline() -> Bool { + + let startingRow: Int = { + if let indexPath = currentArticleIndexPath { + return indexPath.row + } else { + return 0 + } + }() + + for i in startingRow..= shadowTable[indexPath.section].count { + if indexPath.section + 1 >= shadowTable.count { + return IndexPath(row: 0, section: 0) + } else { + return IndexPath(row: 0, section: indexPath.section + 1) + } + } else { + return IndexPath(row: indexPath.row + 1, section: indexPath.section) + } + }() + + if selectNextUnreadFeedFetcher(startingWith: nextIndexPath) { + return + } + selectNextUnreadFeedFetcher(startingWith: IndexPath(row: 0, section: 0)) + + } + + @discardableResult + func selectNextUnreadFeedFetcher(startingWith indexPath: IndexPath) -> Bool { + + for i in indexPath.section.. 0 { + currentMasterIndexPath = nextIndexPath + return true + } + + } + + } + + return false + + } + + // MARK: Fetching Articles + + func fetchArticles() { + + guard let timelineFetcher = timelineFetcher else { + articles = ArticleArray() + return + } + + let fetchedArticles = timelineFetcher.fetchArticles() + updateArticles(with: fetchedArticles) + + } + + func emptyTheTimeline() { + if !articles.isEmpty { + articles = [Article]() + } + } + + func sortDirectionDidChange() { + updateArticles(with: Set(articles)) + } + + func updateArticles(with unsortedArticles: Set
) { + let sortedArticles = Array(unsortedArticles).sortedByDate(sortDirection) + if articles != sortedArticles { + articles = sortedArticles + } + } + + func row(for articleID: String) -> Int? { + updateArticleRowMapIfNeeded() + return articleRowMap[articleID] + } + + func updateArticleRowMap() { + var rowMap = [String: Int]() + var index = 0 + articles.forEach { (article) in + rowMap[article.articleID] = index + index += 1 + } + articleRowMap = rowMap + } + + func updateArticleRowMapIfNeeded() { + if articleRowMap.isEmpty { + updateArticleRowMap() + } + } + + func queueFetchAndMergeArticles() { + AppCoordinator.fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticles)) + } + + @objc func fetchAndMergeArticles() { + + guard let timelineFetcher = timelineFetcher else { + return + } + + var unsortedArticles = timelineFetcher.fetchArticles() + + // Merge articles by articleID. For any unique articleID in current articles, add to unsortedArticles. + let unsortedArticleIDs = unsortedArticles.articleIDs() + for article in articles { + if !unsortedArticleIDs.contains(article.articleID) { + unsortedArticles.insert(article) + } + } + + updateArticles(with: unsortedArticles) + + } + + func timelineFetcherContainsAnyPseudoFeed() -> Bool { + if timelineFetcher is PseudoFeed { + return true + } + return false + } + + func timelineFetcherContainsAnyFeed(_ feeds: Set) -> Bool { + + // Return true if there’s a match or if a folder contains (recursively) one of feeds + + if let feed = timelineFetcher as? Feed { + for oneFeed in feeds { + if feed.feedID == oneFeed.feedID || feed.url == oneFeed.url { + return true + } + } + } else if let folder = timelineFetcher as? Folder { + for oneFeed in feeds { + if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) { + return true + } + } + } + + return false + + } + +} diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 36dedcebf..936684f15 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -23,7 +23,7 @@ class DetailViewController: UIViewController { @IBOutlet weak var browserBarButtonItem: UIBarButtonItem! @IBOutlet weak var webView: WKWebView! - weak var navState: NavigationStateController? + weak var coordinator: AppCoordinator? override func viewDidLoad() { @@ -36,7 +36,7 @@ class DetailViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(articleSelectionDidChange(_:)), name: .ArticleSelectionDidChange, object: navState) + NotificationCenter.default.addObserver(self, selector: #selector(articleSelectionDidChange(_:)), name: .ArticleSelectionDidChange, object: coordinator) NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) } @@ -47,14 +47,14 @@ class DetailViewController: UIViewController { } func markAsRead() { - if let article = navState?.currentArticle { + if let article = coordinator?.currentArticle { markArticles(Set([article]), statusKey: .read, flag: true) } } func updateUI() { - guard let article = navState?.currentArticle else { + guard let article = coordinator?.currentArticle else { nextUnreadBarButtonItem.isEnabled = false prevArticleBarButtonItem.isEnabled = false nextArticleBarButtonItem.isEnabled = false @@ -65,9 +65,9 @@ class DetailViewController: UIViewController { return } - nextUnreadBarButtonItem.isEnabled = navState?.isAnyUnreadAvailable ?? false - prevArticleBarButtonItem.isEnabled = navState?.isPrevArticleAvailable ?? false - nextArticleBarButtonItem.isEnabled = navState?.isNextArticleAvailable ?? false + nextUnreadBarButtonItem.isEnabled = coordinator?.isAnyUnreadAvailable ?? false + prevArticleBarButtonItem.isEnabled = coordinator?.isPrevArticleAvailable ?? false + nextArticleBarButtonItem.isEnabled = coordinator?.isNextArticleAvailable ?? false readBarButtonItem.isEnabled = true starBarButtonItem.isEnabled = true @@ -80,7 +80,7 @@ class DetailViewController: UIViewController { let starImage = article.status.starred ? AppAssets.starClosedImage : AppAssets.starOpenImage starBarButtonItem.image = starImage - if let timelineName = navState?.timelineName { + if let timelineName = coordinator?.timelineName { if navigationController?.navigationItem.backBarButtonItem?.title != timelineName { let backItem = UIBarButtonItem(title: timelineName, style: .plain, target: nil, action: nil) navigationController?.navigationItem.backBarButtonItem = backItem @@ -91,7 +91,7 @@ class DetailViewController: UIViewController { func reloadHTML() { - guard let article = navState?.currentArticle, let webView = webView else { + guard let article = coordinator?.currentArticle, let webView = webView else { return } let style = ArticleStylesManager.shared.currentStyle @@ -110,7 +110,7 @@ class DetailViewController: UIViewController { guard let articles = note.userInfo?[Account.UserInfoKey.articles] as? Set
else { return } - if articles.count == 1 && articles.first?.articleID == navState?.currentArticle?.articleID { + if articles.count == 1 && articles.first?.articleID == coordinator?.currentArticle?.articleID { updateUI() } } @@ -132,41 +132,41 @@ class DetailViewController: UIViewController { // MARK: Actions @IBAction func nextUnread(_ sender: Any) { - navState?.selectNextUnread() + coordinator?.selectNextUnread() } @IBAction func prevArticle(_ sender: Any) { - navState?.currentArticleIndexPath = navState?.prevArticleIndexPath + coordinator?.currentArticleIndexPath = coordinator?.prevArticleIndexPath } @IBAction func nextArticle(_ sender: Any) { - navState?.currentArticleIndexPath = navState?.nextArticleIndexPath + coordinator?.currentArticleIndexPath = coordinator?.nextArticleIndexPath } @IBAction func toggleRead(_ sender: Any) { - if let article = navState?.currentArticle { + if let article = coordinator?.currentArticle { markArticles(Set([article]), statusKey: .read, flag: !article.status.read) } } @IBAction func toggleStar(_ sender: Any) { - if let article = navState?.currentArticle { + if let article = coordinator?.currentArticle { markArticles(Set([article]), statusKey: .starred, flag: !article.status.starred) } } @IBAction func openBrowser(_ sender: Any) { - guard let preferredLink = navState?.currentArticle?.preferredLink, let url = URL(string: preferredLink) else { + guard let preferredLink = coordinator?.currentArticle?.preferredLink, let url = URL(string: preferredLink) else { return } UIApplication.shared.open(url, options: [:]) } @IBAction func showActivityDialog(_ sender: Any) { - guard let preferredLink = navState?.currentArticle?.preferredLink, let url = URL(string: preferredLink) else { + guard let preferredLink = coordinator?.currentArticle?.preferredLink, let url = URL(string: preferredLink) else { return } - let itemSource = ArticleActivityItemSource(url: url, subject: navState?.currentArticle?.title) + let itemSource = ArticleActivityItemSource(url: url, subject: coordinator?.currentArticle?.title) let activityViewController = UIActivityViewController(activityItems: [itemSource], applicationActivities: nil) activityViewController.popoverPresentationController?.barButtonItem = self.actionBarButtonItem diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index dd619e7bb..6dabe827c 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -20,7 +20,7 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn var undoableCommands = [UndoableCommand]() - let navState = NavigationStateController() + weak var coordinator: AppCoordinator! override var canBecomeFirstResponder: Bool { return true } @@ -40,8 +40,8 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange(_:)), name: .AccountStateDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(backingStoresDidRebuild(_:)), name: .BackingStoresDidRebuild, object: navState) - NotificationCenter.default.addObserver(self, selector: #selector(masterSelectionDidChange(_:)), name: .MasterSelectionDidChange, object: navState) + NotificationCenter.default.addObserver(self, selector: #selector(backingStoresDidRebuild(_:)), name: .BackingStoresDidRebuild, object: coordinator) + NotificationCenter.default.addObserver(self, selector: #selector(masterSelectionDidChange(_:)), name: .MasterSelectionDidChange, object: coordinator) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) refreshControl = UIRefreshControl() @@ -82,8 +82,8 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn } if let account = representedObject as? Account { - if let node = navState.rootNode.childNodeRepresentingObject(account) { - let sectionIndex = navState.rootNode.indexOfChild(node)! + if let node = coordinator.rootNode.childNodeRepresentingObject(account) { + let sectionIndex = coordinator.rootNode.indexOfChild(node)! if let headerView = tableView.headerView(forSection: sectionIndex) as? MasterFeedTableViewSectionHeader { headerView.unreadCount = account.unreadCount } @@ -91,8 +91,8 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn return } - guard let node = navState.rootNode.descendantNodeRepresentingObject(representedObject as AnyObject), - let indexPath = navState.indexPathFor(node) else { + guard let node = coordinator.rootNode.descendantNodeRepresentingObject(representedObject as AnyObject), + let indexPath = coordinator.indexPathFor(node) else { return } @@ -119,27 +119,27 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn @objc func userDidAddFeed(_ notification: Notification) { guard let feed = notification.userInfo?[UserInfoKey.feed], - let node = navState.rootNode.descendantNodeRepresentingObject(feed as AnyObject) else { + let node = coordinator.rootNode.descendantNodeRepresentingObject(feed as AnyObject) else { return } - if let indexPath = navState.indexPathFor(node) { + if let indexPath = coordinator.indexPathFor(node) { tableView.scrollToRow(at: indexPath, at: .middle, animated: true) return } // It wasn't already visable, so expand its folder and try again - guard let parent = node.parent, let indexPath = navState.indexPathFor(parent) else { + guard let parent = node.parent, let indexPath = coordinator.indexPathFor(parent) else { return } - navState.expand(indexPath) { [weak self] indexPaths in + coordinator.expand(indexPath) { [weak self] indexPaths in self?.tableView.beginUpdates() self?.tableView.insertRows(at: indexPaths, with: .automatic) self?.tableView.endUpdates() } - if let indexPath = navState.indexPathFor(node) { + if let indexPath = coordinator.indexPathFor(node) { tableView.scrollToRow(at: indexPath, at: .middle, animated: true) } @@ -154,7 +154,7 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn } @objc func masterSelectionDidChange(_ note: Notification) { - if let indexPath = navState.currentMasterIndexPath { + if let indexPath = coordinator.currentMasterIndexPath { if tableView.indexPathForSelectedRow != indexPath { tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle) } @@ -168,16 +168,16 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn // MARK: Table View override func numberOfSections(in tableView: UITableView) -> Int { - return navState.numberOfSections + return coordinator.numberOfSections } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return navState.rowsInSection(section) + return coordinator.rowsInSection(section) } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - guard let nameProvider = navState.rootNode.childAtIndex(section)?.representedObject as? DisplayNameProvider else { + guard let nameProvider = coordinator.rootNode.childAtIndex(section)?.representedObject as? DisplayNameProvider else { return 44 } @@ -191,14 +191,14 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - guard let nameProvider = navState.rootNode.childAtIndex(section)?.representedObject as? DisplayNameProvider else { + guard let nameProvider = coordinator.rootNode.childAtIndex(section)?.representedObject as? DisplayNameProvider else { return nil } let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! MasterFeedTableViewSectionHeader headerView.name = nameProvider.nameForDisplay - guard let sectionNode = navState.rootNode.childAtIndex(section) else { + guard let sectionNode = coordinator.rootNode.childAtIndex(section) else { return headerView } @@ -209,7 +209,7 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn } headerView.tag = section - headerView.disclosureExpanded = navState.isExpanded(sectionNode) + headerView.disclosureExpanded = coordinator.isExpanded(sectionNode) let tap = UITapGestureRecognizer(target: self, action:#selector(self.toggleSectionHeader(_:))) headerView.addGestureRecognizer(tap) @@ -230,7 +230,7 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MasterFeedTableViewCell - guard let node = navState.nodeFor(indexPath) else { + guard let node = coordinator.nodeFor(indexPath) else { return cell } @@ -240,7 +240,7 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn } override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - guard let node = navState.nodeFor(indexPath), !(node.representedObject is PseudoFeed) else { + guard let node = coordinator.nodeFor(indexPath), !(node.representedObject is PseudoFeed) else { return false } return true @@ -273,14 +273,14 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let timeline = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self) - timeline.navState = navState - navState.currentMasterIndexPath = indexPath + timeline.coordinator = coordinator + coordinator.currentMasterIndexPath = indexPath self.navigationController?.pushViewController(timeline, animated: true) } override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { - guard let node = navState.nodeFor(indexPath) else { + guard let node = coordinator.nodeFor(indexPath) else { return false } return node.representedObject is Feed @@ -296,13 +296,13 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn return proposedDestinationIndexPath }() - guard let draggedNode = navState.nodeFor(sourceIndexPath), let destNode = navState.nodeFor(destIndexPath), let parentNode = destNode.parent else { + guard let draggedNode = coordinator.nodeFor(sourceIndexPath), let destNode = coordinator.nodeFor(destIndexPath), let parentNode = destNode.parent else { assertionFailure("This should never happen") return sourceIndexPath } // If this is a folder and isn't expanded or doesn't have any entries, let the users drop on it - if destNode.representedObject is Folder && (destNode.numberOfChildNodes == 0 || !navState.isExpanded(destNode)) { + if destNode.representedObject is Folder && (destNode.numberOfChildNodes == 0 || !coordinator.isExpanded(destNode)) { let movementAdjustment = sourceIndexPath > destIndexPath ? 1 : 0 return IndexPath(row: destIndexPath.row + movementAdjustment, section: destIndexPath.section) } @@ -323,7 +323,7 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn if parentNode.representedObject is Account { return IndexPath(row: 0, section: destIndexPath.section) } else { - return navState.indexPathFor(parentNode)! + return coordinator.indexPathFor(parentNode)! } } else { @@ -333,10 +333,10 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn let movementAdjustment = sourceIndexPath < destIndexPath ? 1 : 0 let adjustedIndex = index - movementAdjustment if adjustedIndex >= sortedNodes.count { - let lastSortedIndexPath = navState.indexPathFor(sortedNodes[sortedNodes.count - 1])! + let lastSortedIndexPath = coordinator.indexPathFor(sortedNodes[sortedNodes.count - 1])! return IndexPath(row: lastSortedIndexPath.row + 1, section: lastSortedIndexPath.section) } else { - return navState.indexPathFor(sortedNodes[adjustedIndex])! + return coordinator.indexPathFor(sortedNodes[adjustedIndex])! } } @@ -345,18 +345,18 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { - guard let sourceNode = navState.nodeFor(sourceIndexPath), let feed = sourceNode.representedObject as? Feed else { + guard let sourceNode = coordinator.nodeFor(sourceIndexPath), let feed = sourceNode.representedObject as? Feed else { return } // Based on the drop we have to determine a node to start looking for a parent container. let destNode: Node = { if destinationIndexPath.row == 0 { - return navState.rootNode.childAtIndex(destinationIndexPath.section)! + return coordinator.rootNode.childAtIndex(destinationIndexPath.section)! } else { let movementAdjustment = sourceIndexPath > destinationIndexPath ? 1 : 0 let adjustedDestIndexPath = IndexPath(row: destinationIndexPath.row - movementAdjustment, section: destinationIndexPath.section) - return navState.nodeFor(adjustedDestIndexPath)! + return coordinator.nodeFor(adjustedDestIndexPath)! } }() @@ -453,22 +453,22 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn @objc func toggleSectionHeader(_ sender: UITapGestureRecognizer) { guard let sectionIndex = sender.view?.tag, - let sectionNode = navState.rootNode.childAtIndex(sectionIndex), + let sectionNode = coordinator.rootNode.childAtIndex(sectionIndex), let headerView = sender.view as? MasterFeedTableViewSectionHeader else { return } - if navState.isExpanded(sectionNode) { + if coordinator.isExpanded(sectionNode) { headerView.disclosureExpanded = false - navState.collapse(section: sectionIndex) { [weak self] indexPaths in + coordinator.collapse(section: sectionIndex) { [weak self] indexPaths in self?.tableView.beginUpdates() self?.tableView.deleteRows(at: indexPaths, with: .automatic) self?.tableView.endUpdates() } } else { headerView.disclosureExpanded = true - navState.expand(section: sectionIndex) { [weak self] indexPaths in + coordinator.expand(section: sectionIndex) { [weak self] indexPaths in self?.tableView.beginUpdates() self?.tableView.insertRows(at: indexPaths, with: .automatic) self?.tableView.endUpdates() @@ -487,7 +487,7 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn } else { cell.indentationLevel = 0 } - cell.disclosureExpanded = navState.isExpanded(node) + cell.disclosureExpanded = coordinator.isExpanded(node) cell.allowDisclosureSelection = node.canHaveChildNodes cell.name = nameFor(node) @@ -525,14 +525,14 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn func delete(indexPath: IndexPath) { guard let undoManager = undoManager, - let deleteNode = navState.nodeFor(indexPath), - let deleteCommand = DeleteCommand(nodesToDelete: [deleteNode], treeController: navState.treeController, undoManager: undoManager, errorHandler: ErrorHandler.present(self)) + let deleteNode = coordinator.nodeFor(indexPath), + let deleteCommand = DeleteCommand(nodesToDelete: [deleteNode], treeController: coordinator.treeController, undoManager: undoManager, errorHandler: ErrorHandler.present(self)) else { return } var deleteIndexPaths = [indexPath] - if navState.isExpanded(deleteNode) { + if coordinator.isExpanded(deleteNode) { for i in 0.. Void) { tableView.visibleCells.forEach { cell in - guard let indexPath = tableView.indexPath(for: cell), let node = navState.nodeFor(indexPath) else { + guard let indexPath = tableView.indexPath(for: cell), let node = coordinator.nodeFor(indexPath) else { return } callback(cell as! MasterFeedTableViewCell, node) @@ -673,7 +673,7 @@ private extension MasterFeedViewController { guard let indexPath = tableView.indexPath(for: cell) else { return } - navState.expand(indexPath) { [weak self] indexPaths in + coordinator.expand(indexPath) { [weak self] indexPaths in self?.tableView.beginUpdates() self?.tableView.insertRows(at: indexPaths, with: .automatic) self?.tableView.endUpdates() @@ -684,7 +684,7 @@ private extension MasterFeedViewController { guard let indexPath = tableView.indexPath(for: cell) else { return } - navState.collapse(indexPath) { [weak self] indexPaths in + coordinator.collapse(indexPath) { [weak self] indexPaths in self?.tableView.beginUpdates() self?.tableView.deleteRows(at: indexPaths, with: .automatic) self?.tableView.endUpdates() diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 9ae9dc8dc..4daa34f76 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -18,7 +18,7 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand @IBOutlet weak var markAllAsReadButton: UIBarButtonItem! @IBOutlet weak var firstUnreadButton: UIBarButtonItem! - weak var navState: NavigationStateController? + weak var coordinator: AppCoordinator! var undoableCommands = [UndoableCommand]() override var canBecomeFirstResponder: Bool { @@ -37,10 +37,10 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(articlesReinitialized(_:)), name: .ArticlesReinitialized, object: navState) - NotificationCenter.default.addObserver(self, selector: #selector(articleDataDidChange(_:)), name: .ArticleDataDidChange, object: navState) - NotificationCenter.default.addObserver(self, selector: #selector(articlesDidChange(_:)), name: .ArticlesDidChange, object: navState) - NotificationCenter.default.addObserver(self, selector: #selector(articleSelectionDidChange(_:)), name: .ArticleSelectionDidChange, object: navState) + NotificationCenter.default.addObserver(self, selector: #selector(articlesReinitialized(_:)), name: .ArticlesReinitialized, object: coordinator) + NotificationCenter.default.addObserver(self, selector: #selector(articleDataDidChange(_:)), name: .ArticleDataDidChange, object: coordinator) + NotificationCenter.default.addObserver(self, selector: #selector(articlesDidChange(_:)), name: .ArticlesDidChange, object: coordinator) + NotificationCenter.default.addObserver(self, selector: #selector(articleSelectionDidChange(_:)), name: .ArticleSelectionDidChange, object: coordinator) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) refreshControl = UIRefreshControl() @@ -66,7 +66,7 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "showDetail" { let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController - controller.navState = navState + controller.coordinator = coordinator controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem controller.navigationItem.leftItemsSupplementBackButton = true splitViewController?.toggleMasterView() @@ -101,7 +101,7 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand let markTitle = NSLocalizedString("Mark All Read", comment: "Mark All Read") let markAction = UIAlertAction(title: markTitle, style: .default) { [weak self] (action) in - guard let articles = self?.navState?.articles, + guard let articles = self?.coordinator.articles, let undoManager = self?.undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else { return @@ -119,7 +119,7 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand } @IBAction func firstUnread(_ sender: Any) { - if let indexPath = navState?.firstUnreadArticleIndexPath { + if let indexPath = coordinator.firstUnreadArticleIndexPath { tableView.scrollToRow(at: indexPath, at: .middle, animated: true) } } @@ -131,14 +131,12 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return navState?.articles.count ?? 0 + return coordinator.articles.count } override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - guard let article = navState?.articles[indexPath.row] else { - return nil - } + let article = coordinator.articles[indexPath.row] // Set up the read action let readTitle = article.status.read ? @@ -180,20 +178,14 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MasterTimelineTableViewCell - - guard let article = navState?.articles[indexPath.row] else { - return cell - } - + let article = coordinator.articles[indexPath.row] configureTimelineCell(cell, article: article) - return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - navState?.currentArticleIndexPath = indexPath + coordinator.currentArticleIndexPath = indexPath } // MARK: Notifications @@ -219,7 +211,7 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand performBlockAndRestoreSelection { tableView.indexPathsForVisibleRows?.forEach { indexPath in - guard let article = navState?.articles.articleAtRow(indexPath.row) else { + guard let article = coordinator.articles.articleAtRow(indexPath.row) else { return } @@ -235,14 +227,14 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand @objc func avatarDidBecomeAvailable(_ note: Notification) { - guard navState?.showAvatars ?? false, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else { + guard coordinator.showAvatars, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else { return } performBlockAndRestoreSelection { tableView.indexPathsForVisibleRows?.forEach { indexPath in - guard let article = navState?.articles.articleAtRow(indexPath.row), let authors = article.authors, !authors.isEmpty else { + guard let article = coordinator.articles.articleAtRow(indexPath.row), let authors = article.authors, !authors.isEmpty else { return } @@ -258,7 +250,7 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand } @objc func imageDidBecomeAvailable(_ note: Notification) { - if navState?.showAvatars ?? false { + if coordinator.showAvatars { queueReloadVisableCells() } } @@ -287,7 +279,7 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand @objc func articleSelectionDidChange(_ note: Notification) { - if let indexPath = navState?.currentArticleIndexPath { + if let indexPath = coordinator.currentArticleIndexPath { if tableView.indexPathForSelectedRow != indexPath { tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle) } @@ -323,9 +315,8 @@ class MasterTimelineViewController: ProgressTableViewController, UndoableCommand if articleIDs.isEmpty { return } - if let indexes = navState?.indexesForArticleIDs(articleIDs) { - reloadVisibleCells(for: indexes) - } + let indexes = coordinator.indexesForArticleIDs(articleIDs) + reloadVisibleCells(for: indexes) } private func reloadVisibleCells(for indexes: IndexSet) { @@ -373,10 +364,10 @@ private extension MasterTimelineViewController { func resetUI() { - title = navState?.timelineName - navigationController?.title = navState?.timelineName + title = coordinator.timelineName + navigationController?.title = coordinator.timelineName - if navState?.articles.count ?? 0 > 0 { + if coordinator.articles.count > 0 { tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) } @@ -385,8 +376,8 @@ private extension MasterTimelineViewController { } func updateUI() { - markAllAsReadButton.isEnabled = navState?.isTimelineUnreadAvailable ?? false - firstUnreadButton.isEnabled = navState?.isTimelineUnreadAvailable ?? false + markAllAsReadButton.isEnabled = coordinator.isTimelineUnreadAvailable + firstUnreadButton.isEnabled = coordinator.isTimelineUnreadAvailable } func configureTimelineCell(_ cell: MasterTimelineTableViewCell, article: Article) { @@ -394,15 +385,15 @@ private extension MasterTimelineViewController { let avatar = avatarFor(article) let featuredImage = featuredImageFor(article) - let showFeedNames = navState?.showFeedNames ?? false - let showAvatar = navState?.showAvatars ?? false && avatar != nil + let showFeedNames = coordinator.showFeedNames + let showAvatar = coordinator.showAvatars && avatar != nil cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, avatar: avatar, showAvatar: showAvatar, featuredImage: featuredImage, numberOfLines: numberOfTextLines) } func avatarFor(_ article: Article) -> UIImage? { - if !(navState?.showAvatars ?? false) { + if !coordinator.showAvatars { return nil } diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index b9ec7d4de..c61444e05 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -8,8 +8,10 @@ import UIKit -class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewControllerDelegate { - +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var coordinator = AppCoordinator() + var window: UIWindow? // UIWindowScene delegate @@ -19,9 +21,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewControllerDe window!.tintColor = AppAssets.netNewsWireBlueColor let splitViewController = UIStoryboard.main.instantiateInitialViewController() as! UISplitViewController - splitViewController.delegate = self + splitViewController.delegate = coordinator window!.rootViewController = splitViewController - + + let masterNavigationController = splitViewController.viewControllers[0] as! UINavigationController + let masterFeedViewController = masterNavigationController.topViewController as! MasterFeedViewController + masterFeedViewController.coordinator = coordinator + let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem @@ -67,16 +73,4 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewControllerDe // return false // } - // MARK: UISplitViewControllerDelegate - - func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { - guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false } - guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false } - if topAsDetailController.navState?.currentArticle == nil { - // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded. - return true - } - return false - } - }