From 61496e138f475aa003b54aedb5fa22c872c3f87f Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 3 Mar 2024 22:51:53 -0800 Subject: [PATCH] =?UTF-8?q?Restore=20several=20of=20Maurice=E2=80=99s=20ch?= =?UTF-8?q?anges,=20including=20the=20new=20three-pane=20split=20view.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NetNewsWire.xcodeproj/project.pbxproj | 8 +- Shared/AppNotifications.swift | 2 + Shared/UserInfoKey.swift | 3 +- iOS/Article/ArticleViewController.swift | 43 +- iOS/Base.lproj/LaunchScreenPad.storyboard | 32 +- iOS/Base.lproj/LaunchScreenPhone.storyboard | 32 +- iOS/Base.lproj/Main.storyboard | 119 +++- iOS/Feeds/FeedsViewController+Drag.swift | 2 +- iOS/Feeds/FeedsViewController+Drop.swift | 2 +- iOS/Feeds/ShadowTableChanges.swift | 10 +- ...ller.swift => SidebarViewController.swift} | 12 +- iOS/RootSplitViewController.swift | 12 +- iOS/SceneCoordinator.swift | 559 +++++++----------- iOS/SceneDelegate.swift | 26 +- iOS/Timeline/TimelineViewController.swift | 5 - 15 files changed, 449 insertions(+), 418 deletions(-) rename iOS/Feeds/{FeedsViewController.swift => SidebarViewController.swift} (99%) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index eea021437..63421d43b 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -299,7 +299,7 @@ 51C45268226508F600C03939 /* FeedUnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45261226508F600C03939 /* FeedUnreadCountView.swift */; }; 51C45269226508F600C03939 /* FeedTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45262226508F600C03939 /* FeedTableViewCell.swift */; }; 51C4526A226508F600C03939 /* FeedTableViewCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45263226508F600C03939 /* FeedTableViewCellLayout.swift */; }; - 51C4526B226508F600C03939 /* FeedsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45264226508F600C03939 /* FeedsViewController.swift */; }; + 51C4526B226508F600C03939 /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45264226508F600C03939 /* SidebarViewController.swift */; }; 51C452762265091600C03939 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4526E2265091600C03939 /* TimelineViewController.swift */; }; 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452702265091600C03939 /* MultilineUILabelSizer.swift */; }; 51C452782265091600C03939 /* TimelineCellData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452712265091600C03939 /* TimelineCellData.swift */; }; @@ -1241,7 +1241,7 @@ 51C45261226508F600C03939 /* FeedUnreadCountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedUnreadCountView.swift; sourceTree = ""; }; 51C45262226508F600C03939 /* FeedTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedTableViewCell.swift; sourceTree = ""; }; 51C45263226508F600C03939 /* FeedTableViewCellLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedTableViewCellLayout.swift; sourceTree = ""; }; - 51C45264226508F600C03939 /* FeedsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedsViewController.swift; sourceTree = ""; }; + 51C45264226508F600C03939 /* SidebarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarViewController.swift; sourceTree = ""; }; 51C4526E2265091600C03939 /* TimelineViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineViewController.swift; sourceTree = ""; }; 51C452702265091600C03939 /* MultilineUILabelSizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultilineUILabelSizer.swift; sourceTree = ""; }; 51C452712265091600C03939 /* TimelineCellData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineCellData.swift; sourceTree = ""; }; @@ -1950,7 +1950,7 @@ 51C4525D226508F600C03939 /* Feeds */ = { isa = PBXGroup; children = ( - 51C45264226508F600C03939 /* FeedsViewController.swift */, + 51C45264226508F600C03939 /* SidebarViewController.swift */, 51627A6623861DA3007B3B4B /* FeedsViewController+Drag.swift */, 51627A6823861DED007B3B4B /* FeedsViewController+Drop.swift */, 51CE1C0A23622006005548FC /* RefreshProgressView.swift */, @@ -3961,7 +3961,7 @@ 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */, 51F9F3F923DFB16300A314FD /* UITableView-Extensions.swift in Sources */, 51C452792265091600C03939 /* TimelineTableViewCell.swift in Sources */, - 51C4526B226508F600C03939 /* FeedsViewController.swift in Sources */, + 51C4526B226508F600C03939 /* SidebarViewController.swift in Sources */, 5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */, 84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */, 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */, diff --git a/Shared/AppNotifications.swift b/Shared/AppNotifications.swift index 84cfd9a72..7207f2430 100644 --- a/Shared/AppNotifications.swift +++ b/Shared/AppNotifications.swift @@ -10,8 +10,10 @@ import Foundation import Articles extension Notification.Name { + static let InspectableObjectsDidChange = Notification.Name("TimelineSelectionDidChangeNotification") static let UserDidAddFeed = Notification.Name("UserDidAddFeedNotification") + static let LaunchedFromExternalAction = Notification.Name("LaunchedFromExternalAction") #if !MAC_APP_STORE static let WebInspectorEnabledDidChange = Notification.Name("WebInspectorEnabledDidChange") diff --git a/Shared/UserInfoKey.swift b/Shared/UserInfoKey.swift index 35a392f25..e410d4c45 100644 --- a/Shared/UserInfoKey.swift +++ b/Shared/UserInfoKey.swift @@ -25,5 +25,6 @@ struct UserInfoKey { static let selectedFeedsState = "selectedFeedsState" static let isShowingExtractedArticle = "isShowingExtractedArticle" static let articleWindowScrollY = "articleWindowScrollY" - + static let isSidebarHidden = "isSidebarHidden" + } diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index 4fbdec7df..1b23925fc 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -45,6 +45,8 @@ class ArticleViewController: UIViewController { weak var coordinator: SceneCoordinator! + private let poppableDelegate = PoppableGestureRecognizerDelegate() + var article: Article? { didSet { if let controller = currentWebViewController, controller.article != article { @@ -82,6 +84,8 @@ class ArticleViewController: UIViewController { return keyboardManager.keyCommands } + private var lastKnownDisplayMode : UISplitViewController.DisplayMode? + override func viewDidLoad() { super.viewDidLoad() @@ -100,7 +104,14 @@ class ArticleViewController: UIViewController { articleExtractorButton.addTarget(self, action: #selector(toggleArticleExtractor(_:)), for: .touchUpInside) toolbarItems?.insert(UIBarButtonItem(customView: articleExtractorButton), at: 6) - + + if let parentNavController = navigationController?.parent as? UINavigationController { + poppableDelegate.navigationController = parentNavController + parentNavController.interactivePopGestureRecognizer?.delegate = poppableDelegate + } + + navigationItem.leftItemsSupplementBackButton = true + pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:]) pageViewController.delegate = self pageViewController.dataSource = self @@ -155,12 +166,17 @@ class ArticleViewController: UIViewController { updateUI() } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(true) - coordinator.isArticleViewControllerPending = false + override func viewWillAppear(_ animated: Bool) { + navigationController?.isToolbarHidden = false + if AppDefaults.shared.articleFullscreenEnabled { + currentWebViewController?.hideBars() + } + + super.viewWillAppear(animated) } - + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) if searchBar != nil && !searchBar.isHidden { endFind() } @@ -211,9 +227,12 @@ class ArticleViewController: UIViewController { starBarButtonItem.image = AppAssets.starOpenImage starBarButtonItem.accLabelText = NSLocalizedString("Star Article", comment: "Star Article") } - } - + + override func contentScrollView(for edge: NSDirectionalRectEdge) -> UIScrollView? { + return currentWebViewController?.webView?.scrollView + } + // MARK: Notifications @objc dynamic func unreadCountDidChange(_ notification: Notification) { @@ -324,6 +343,10 @@ class ArticleViewController: UIViewController { func setScrollPosition(isShowingExtractedArticle: Bool, articleWindowScrollY: Int) { currentWebViewController?.setScrollPosition(isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY) } + + public func splitViewControllerWillChangeTo(displayMode: UISplitViewController.DisplayMode) { + lastKnownDisplayMode = displayMode + } } // MARK: Find in Article @@ -447,9 +470,11 @@ extension ArticleViewController: UIPageViewControllerDelegate { coordinator.selectArticle(article, animations: [.select, .scroll, .navigation]) articleExtractorButton.buttonState = currentWebViewController?.articleExtractorButtonState ?? .off - previousViewControllers.compactMap({ $0 as? WebViewController }).forEach({ $0.stopWebViewActivity() }) + let webViewControllers = previousViewControllers.compactMap{ $0 as? WebViewController } + for webViewController in webViewControllers { + webViewController.stopWebViewActivity() + } } - } // MARK: UIGestureRecognizerDelegate diff --git a/iOS/Base.lproj/LaunchScreenPad.storyboard b/iOS/Base.lproj/LaunchScreenPad.storyboard index 06c951431..c7cbea4ec 100644 --- a/iOS/Base.lproj/LaunchScreenPad.storyboard +++ b/iOS/Base.lproj/LaunchScreenPad.storyboard @@ -1,9 +1,11 @@ - + - + + + @@ -14,8 +16,8 @@ - + @@ -33,7 +35,7 @@ - + @@ -41,7 +43,7 @@ - + @@ -49,7 +51,7 @@ - + @@ -65,12 +67,19 @@ - + + + + + + + + @@ -83,7 +92,7 @@ - + @@ -113,7 +122,10 @@ - - + + + + + diff --git a/iOS/Base.lproj/LaunchScreenPhone.storyboard b/iOS/Base.lproj/LaunchScreenPhone.storyboard index 61a86ee99..e77de49db 100644 --- a/iOS/Base.lproj/LaunchScreenPhone.storyboard +++ b/iOS/Base.lproj/LaunchScreenPhone.storyboard @@ -1,9 +1,11 @@ - + - + + + @@ -14,8 +16,8 @@ - + @@ -33,7 +35,7 @@ - + @@ -41,7 +43,7 @@ - + @@ -49,7 +51,7 @@ - + @@ -65,12 +67,19 @@ - + + + + + + + + @@ -83,7 +92,7 @@ - + @@ -113,7 +122,10 @@ - - + + + + + diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard index 6507aaa58..b8b7f6e61 100644 --- a/iOS/Base.lproj/Main.storyboard +++ b/iOS/Base.lproj/Main.storyboard @@ -1,8 +1,9 @@ - + - + + @@ -12,12 +13,12 @@ - + - + + + @@ -41,6 +45,9 @@ + + + @@ -50,6 +57,9 @@ + + + @@ -61,6 +71,9 @@ + + + @@ -73,6 +86,9 @@ + + + @@ -82,6 +98,9 @@ + + + @@ -90,12 +109,21 @@ + + + + + + + + + @@ -107,18 +135,18 @@ - + - - - + + + - + @@ -133,7 +161,10 @@ - + + + + @@ -144,7 +175,11 @@ - + + + + + @@ -153,18 +188,32 @@ - + + + + + + + + + + + + + + + - + - + - + @@ -182,6 +231,9 @@ + + + @@ -192,13 +244,19 @@ + + + - + + + + @@ -206,9 +264,15 @@ + + + + + + @@ -216,7 +280,7 @@ - + @@ -316,28 +380,40 @@ @@ -396,15 +472,16 @@ + + - - + diff --git a/iOS/Feeds/FeedsViewController+Drag.swift b/iOS/Feeds/FeedsViewController+Drag.swift index 615c078a6..fac27383d 100644 --- a/iOS/Feeds/FeedsViewController+Drag.swift +++ b/iOS/Feeds/FeedsViewController+Drag.swift @@ -11,7 +11,7 @@ import MobileCoreServices import Account import UniformTypeIdentifiers -extension FeedsViewController: UITableViewDragDelegate { +extension SidebarViewController: UITableViewDragDelegate { func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { guard let node = coordinator.nodeFor(indexPath), let feed = node.representedObject as? Feed else { diff --git a/iOS/Feeds/FeedsViewController+Drop.swift b/iOS/Feeds/FeedsViewController+Drop.swift index 1b046f8e4..c8ef1986d 100644 --- a/iOS/Feeds/FeedsViewController+Drop.swift +++ b/iOS/Feeds/FeedsViewController+Drop.swift @@ -11,7 +11,7 @@ import RSCore import Account import RSTree -extension FeedsViewController: UITableViewDropDelegate { +extension SidebarViewController: UITableViewDropDelegate { func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { return session.localDragSession != nil diff --git a/iOS/Feeds/ShadowTableChanges.swift b/iOS/Feeds/ShadowTableChanges.swift index dd8a12801..c14b9c698 100644 --- a/iOS/Feeds/ShadowTableChanges.swift +++ b/iOS/Feeds/ShadowTableChanges.swift @@ -25,6 +25,7 @@ struct ShadowTableChanges { var section: Int var deletes: Set? var inserts: Set? + var reloads: Set? var moves: Set? var isEmpty: Bool { @@ -41,18 +42,23 @@ struct ShadowTableChanges { return inserts.map { IndexPath(row: $0, section: section) } } + var reloadIndexPaths: [IndexPath]? { + guard let reloads = reloads else { return nil } + return reloads.map { IndexPath(row: $0, section: section) } + } + var moveIndexPaths: [(IndexPath, IndexPath)]? { guard let moves = moves else { return nil } return moves.map { (IndexPath(row: $0.from, section: section), IndexPath(row: $0.to, section: section)) } } - init(section: Int, deletes: Set?, inserts: Set?, moves: Set?) { + init(section: Int, deletes: Set?, inserts: Set?, reloads: Set?, moves: Set?) { self.section = section self.deletes = deletes self.inserts = inserts + self.reloads = reloads self.moves = moves } - } var deletes: Set? diff --git a/iOS/Feeds/FeedsViewController.swift b/iOS/Feeds/SidebarViewController.swift similarity index 99% rename from iOS/Feeds/FeedsViewController.swift rename to iOS/Feeds/SidebarViewController.swift index ea06ea63a..8eb919da4 100644 --- a/iOS/Feeds/FeedsViewController.swift +++ b/iOS/Feeds/SidebarViewController.swift @@ -13,7 +13,7 @@ import RSCore import RSTree import SafariServices -class FeedsViewController: UITableViewController, UndoableCommandRunner { +class SidebarViewController: UITableViewController, UndoableCommandRunner { @IBOutlet weak var filterButton: UIBarButtonItem! private var refreshProgressView: RefreshProgressView? @@ -628,7 +628,7 @@ class FeedsViewController: UITableViewController, UndoableCommandRunner { // MARK: UIContextMenuInteractionDelegate -extension FeedsViewController: UIContextMenuInteractionDelegate { +extension SidebarViewController: UIContextMenuInteractionDelegate { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { guard let sectionIndex = interaction.view?.tag, @@ -666,7 +666,7 @@ extension FeedsViewController: UIContextMenuInteractionDelegate { // MARK: FeedTableViewSectionHeaderDelegate -extension FeedsViewController: FeedTableViewSectionHeaderDelegate { +extension SidebarViewController: FeedTableViewSectionHeaderDelegate { func FeedTableViewSectionHeaderDisclosureDidToggle(_ sender: FeedTableViewSectionHeader) { toggle(sender) @@ -676,7 +676,7 @@ extension FeedsViewController: FeedTableViewSectionHeaderDelegate { // MARK: TableViewCellDelegate -extension FeedsViewController: FeedTableViewCellDelegate { +extension SidebarViewController: FeedTableViewCellDelegate { func feedTableViewCellDisclosureDidToggle(_ sender: FeedTableViewCell, expanding: Bool) { if expanding { @@ -690,7 +690,7 @@ extension FeedsViewController: FeedTableViewCellDelegate { // MARK: Private -private extension FeedsViewController { +private extension SidebarViewController { func configureToolbar() { guard let refreshProgressView = Bundle.main.loadNibNamed("RefreshProgressView", owner: self, options: nil)?[0] as? RefreshProgressView else { @@ -1230,7 +1230,7 @@ private extension FeedsViewController { } -extension FeedsViewController: UIGestureRecognizerDelegate { +extension SidebarViewController: UIGestureRecognizerDelegate { func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { guard let gestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else { return false diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 073aa69fb..08ff091ac 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -9,8 +9,8 @@ import UIKit import Account -class RootSplitViewController: UISplitViewController { - +final class RootSplitViewController: UISplitViewController { + var coordinator: SceneCoordinator! override var prefersStatusBarHidden: Bool { @@ -25,11 +25,11 @@ class RootSplitViewController: UISplitViewController { coordinator.resetFocus() } - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - self.coordinator.configurePanelMode(for: size) - super.viewWillTransition(to: size, with: coordinator) + override func show(_ column: UISplitViewController.Column) { + guard !coordinator.isNavigationDisabled else { return } + super.show(column) } - + // MARK: Keyboard Shortcuts @objc func scrollOrGoToNextUnread(_ sender: Any?) { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 841a917ec..d2d77a1a9 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -13,11 +13,17 @@ import Articles import RSCore import RSTree import SafariServices +import SwiftUI -enum PanelMode { - case unset - case three - case standard +protocol MainControllerIdentifiable { + var mainControllerIdentifier: MainControllerIdentifier { get } +} + +enum MainControllerIdentifier { + case none + case mainFeed + case mainTimeline + case article } enum SearchScope: Int { @@ -52,41 +58,30 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { return rootSplitViewController.undoManager } - private var panelMode: PanelMode = .unset - private var activityManager = ActivityManager() private var rootSplitViewController: RootSplitViewController! - private var navigationController: UINavigationController! - private var feedsViewController: FeedsViewController! + private var sidebarViewController: SidebarViewController! private var timelineViewController: TimelineViewController? - private var subSplitViewController: UISplitViewController? - - private var articleViewController: ArticleViewController? { - if let detail = navigationController.viewControllers.last as? ArticleViewController { - return detail - } - if let subSplit = subSplitViewController { - if let navController = subSplit.viewControllers.last as? UINavigationController { - return navController.topViewController as? ArticleViewController - } - } else { - if let navController = rootSplitViewController.viewControllers.last as? UINavigationController { - return navController.topViewController as? ArticleViewController - } - } - return nil - } - - private var wasRootSplitViewControllerCollapsed = false - + private var articleViewController: ArticleViewController? + + private var lastMainControllerToAppear = MainControllerIdentifier.none + private let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5) private let rebuildBackingStoresQueue = CoalescingQueue(name: "Rebuild The Backing Stores", interval: 0.5) private var fetchSerialNumber = 0 private let fetchRequestQueue = FetchRequestQueue() + /// Which Containers are expanded private var expandedTable = Set() + + /// Which Containers used to be expanded. Reset by rebuilding the Shadow Table. + private var lastExpandedTable = Set() + + /// Which Feeds have the Read Articles Filter enabled private var readFilterEnabledTable = [SidebarItemIdentifier: Bool]() + + /// Flattened tree structure for the Sidebar private var shadowTable = [(sectionID: String, feedNodes: [FeedNode])]() private(set) var preSearchTimelineFeed: SidebarItem? @@ -95,10 +90,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { private var isSearching: Bool = false private var savedSearchArticles: ArticleArray? = nil private var savedSearchArticleIds: Set? = nil - - var isTimelineViewControllerPending = false - var isArticleViewControllerPending = false - + private(set) var sortDirection = AppDefaults.shared.timelineSortDirection { didSet { if sortDirection != oldValue { @@ -134,14 +126,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { return activity } + var isNavigationDisabled = false + var isRootSplitCollapsed: Bool { return rootSplitViewController.isCollapsed } - - var isThreePanelMode: Bool { - return panelMode == .three - } - + var isReadFeedsFiltered: Bool { return treeControllerDelegate.isReadFiltered } @@ -292,11 +282,32 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { var timelineUnreadCount: Int = 0 - override init() { - treeController = TreeController(delegate: treeControllerDelegate) + init(rootSplitViewController: RootSplitViewController) { + self.rootSplitViewController = rootSplitViewController + self.treeController = TreeController(delegate: treeControllerDelegate) super.init() + self.sidebarViewController = rootSplitViewController.viewController(for: .primary) as? SidebarViewController + self.sidebarViewController.coordinator = self + if let navController = self.sidebarViewController?.navigationController { + navController.delegate = self + configureNavigationController(navController) + } + + self.timelineViewController = rootSplitViewController.viewController(for: .supplementary) as? TimelineViewController + self.timelineViewController?.coordinator = self + if let navController = self.timelineViewController?.navigationController { + navController.delegate = self + configureNavigationController(navController) + } + + self.articleViewController = rootSplitViewController.viewController(for: .secondary) as? ArticleViewController + self.articleViewController?.coordinator = self + if let navController = self.articleViewController?.navigationController { + configureNavigationController(navController) + } + for sectionNode in treeController.rootNode.childNodes { markExpanded(sectionNode) shadowTable.append((sectionID: "", feedNodes: [FeedNode]())) @@ -319,30 +330,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { NotificationCenter.default.addObserver(self, selector: #selector(themeDownloadDidFail(_:)), name: .didFailToImportThemeWithError, object: nil) } - func start(for size: CGSize) -> UIViewController { - rootSplitViewController = RootSplitViewController() - rootSplitViewController.coordinator = self - rootSplitViewController.preferredDisplayMode = .oneBesideSecondary - rootSplitViewController.viewControllers = [InteractiveNavigationController.template()] - rootSplitViewController.delegate = self - - navigationController = (rootSplitViewController.viewControllers.first as! UINavigationController) - navigationController.delegate = self - - feedsViewController = UIStoryboard.main.instantiateController(ofType: FeedsViewController.self) - feedsViewController.coordinator = self - navigationController.pushViewController(feedsViewController, animated: false) - - let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) - articleViewController.coordinator = self - let detailNavigationController = addNavControllerIfNecessary(articleViewController, showButton: true) - rootSplitViewController.showDetailViewController(detailNavigationController, sender: self) - - configurePanelMode(for: size) - - return rootSplitViewController - } - func restoreWindowState(_ activity: NSUserActivity?) { if let activity = activity, let windowState = activity.userInfo?[UserInfoKey.windowState] as? [AnyHashable: Any] { @@ -359,6 +346,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } } +// if let isSidebarHidden = windowState[UserInfoKey.isSidebarHidden] as? Bool, isSidebarHidden { +// DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { +// self.rootSplitViewController.preferredDisplayMode = .secondaryOnly +// } +// } + rebuildBackingStores(initialLoad: true) // You can't assign the Feeds Read Filter until we've built the backing stores at least once or there is nothing @@ -397,31 +390,11 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { handleReadArticle(userInfo) } - func configurePanelMode(for size: CGSize) { - guard rootSplitViewController.traitCollection.userInterfaceIdiom == .pad else { - return - } - - if (size.width / size.height) > 1.2 { - if panelMode == .unset || panelMode == .standard { - panelMode = .three - configureThreePanelMode() - } - } else { - if panelMode == .unset || panelMode == .three { - panelMode = .standard - configureStandardPanelMode() - } - } - - wasRootSplitViewControllerCollapsed = rootSplitViewController.isCollapsed - } - func resetFocus() { if currentArticle != nil { timelineViewController?.focus() } else { - feedsViewController?.focus() + sidebarViewController?.focus() } } @@ -436,7 +409,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { func showSearch() { selectFeed(indexPath: nil) { - self.installTimelineControllerIfNecessary(animated: false) + self.rootSplitViewController.show(.supplementary) DispatchQueue.main.asyncAfter(deadline: .now()) { self.timelineViewController!.showSearchAll() } @@ -611,7 +584,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { treeControllerDelegate.isReadFiltered = true } rebuildBackingStores() - feedsViewController?.updateUI() + sidebarViewController?.updateUI() } func toggleReadArticlesFilter() { @@ -646,16 +619,33 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { return shadowTable[section].feedNodes.count } + func nodeFor(_ section: Int) -> Node? { + return treeController.rootNode.childAtIndex(section) + } + func nodeFor(_ indexPath: IndexPath) -> Node? { - guard indexPath.section < shadowTable.count && indexPath.row < shadowTable[indexPath.section].feedNodes.count else { + guard indexPath.section > -1 && + indexPath.row > -1 && + indexPath.section < shadowTable.count && + indexPath.row < shadowTable[indexPath.section].feedNodes.count else { return nil } return shadowTable[indexPath.section].feedNodes[indexPath.row].node } - + + func indexPathFor(_ object: AnyObject) -> IndexPath? { + guard let node = treeController.rootNode.descendantNodeRepresentingObject(object) else { + return nil + } + return indexPathFor(node) + } + func indexPathFor(_ node: Node) -> IndexPath? { + + let feedNode = FeedNode(node) + for i in 0.. Void)? = nil) { markAllAsRead(articles) { - self.navigationController.popViewController(animated: true) + self.rootSplitViewController.show(.primary) completion?() } } @@ -1109,7 +1100,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { return timelineFeed == feed } - func discloseFeed(_ feed: Feed, animations: Animations = [], completion: (() -> Void)? = nil) { + func discloseFeed(_ feed: Feed, initialLoad: Bool = false, animations: Animations = [], completion: (() -> Void)? = nil) { if isSearching { timelineViewController?.hideSearch() } @@ -1133,7 +1124,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { self.treeControllerDelegate.addFilterException(parentFolderFeedID) } - rebuildBackingStores(completion: { + rebuildBackingStores(initialLoad: initialLoad, completion: { self.treeControllerDelegate.resetFilterExceptions() self.selectFeed(feed, animations: animations, completion: completion) }) @@ -1162,7 +1153,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { settingsViewController.presentingParentController = rootSplitViewController rootSplitViewController.present(settingsNavController, animated: true) } - + func showAccountInspector(for account: Account) { let accountInspectorNavController = UIStoryboard.inspector.instantiateViewController(identifier: "AccountInspectorNavigationViewController") as! UINavigationController @@ -1173,7 +1164,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { accountInspectorController.account = account rootSplitViewController.present(accountInspectorNavController, animated: true) } - + func showFeedInspector() { let timelineFeed = timelineFeed as? Feed let articleFeed = currentArticle?.feed @@ -1192,7 +1183,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { feedInspectorController.feed = feed rootSplitViewController.present(feedInspectorNavController, animated: true) } - + func showAddFeed(initialFeed: String? = nil, initialFeedName: String? = nil) { // Since Add Feed can be opened from anywhere with a keyboard shortcut, we have to deselect any currently selected feeds @@ -1206,14 +1197,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { addNavViewController.modalPresentationStyle = .formSheet addNavViewController.preferredContentSize = AddFeedViewController.preferredContentSizeForFormSheetDisplay - feedsViewController.present(addNavViewController, animated: true) + sidebarViewController.present(addNavViewController, animated: true) } func showAddFolder() { let addNavViewController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddFolderViewControllerNav") as! UINavigationController addNavViewController.modalPresentationStyle = .formSheet addNavViewController.preferredContentSize = AddFolderViewController.preferredContentSizeForFormSheetDisplay - feedsViewController.present(addNavViewController, animated: true) + sidebarViewController.present(addNavViewController, animated: true) } func showFullScreenImage(image: UIImage, imageTitle: String?, transitioningDelegate: UIViewControllerTransitioningDelegate) { @@ -1262,12 +1253,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { articleViewController?.openInAppBrowser() } else { - feedsViewController.openInAppBrowser() + sidebarViewController.openInAppBrowser() } } func navigateToFeeds() { - feedsViewController?.focus() + sidebarViewController?.focus() selectArticle(nil) } @@ -1309,75 +1300,74 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { /// `SFSafariViewController` or `SettingsViewController`, /// otherwise, this function does nothing. func dismissIfLaunchingFromExternalAction() { - guard let presentedController = feedsViewController.presentedViewController else { return } + guard let presentedController = sidebarViewController.presentedViewController else { return } if presentedController.isKind(of: SFSafariViewController.self) { presentedController.dismiss(animated: true, completion: nil) } - guard let settings = presentedController.children.first as? SettingsViewController else { return } - settings.dismiss(animated: true, completion: nil) + + // There's no obvious way to detect if the presented controller + // is the SwiftUI UIHostingController. Posting a notification + // which it can react to seems to be the simplest solution. + NotificationCenter.default.post(name: .LaunchedFromExternalAction, object: nil) } - } // MARK: UISplitViewControllerDelegate extension SceneCoordinator: UISplitViewControllerDelegate { - - func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { - timelineViewController?.updateUI() - guard !isThreePanelMode else { - return true - } - - if let articleViewController = (secondaryViewController as? UINavigationController)?.topViewController as? ArticleViewController { - if currentArticle != nil { - navigationController.pushViewController(articleViewController, animated: false) + func splitViewController(_ svc: UISplitViewController, topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column) -> UISplitViewController.Column { + switch proposedTopColumn { + case .supplementary: + if currentFeedIndexPath != nil { + return .supplementary + } else { + return .primary } + case .secondary: + if currentArticle != nil { + return .secondary + } else { + if currentFeedIndexPath != nil { + return .supplementary + } else { + return .primary + } + } + default: + return .primary } - - return true } - - func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? { - timelineViewController?.updateUI() - guard !isThreePanelMode else { - return subSplitViewController - } - - if let articleViewController = navigationController.viewControllers.last as? ArticleViewController { - articleViewController.showBars(self) - navigationController.popViewController(animated: false) - let controller = addNavControllerIfNecessary(articleViewController, showButton: true) - return controller - } - - if currentArticle == nil { - let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) - articleViewController.coordinator = self - let controller = addNavControllerIfNecessary(articleViewController, showButton: true) - return controller - } - - return nil + func splitViewController(_ svc: UISplitViewController, willChangeTo displayMode: UISplitViewController.DisplayMode) { + articleViewController?.splitViewControllerWillChangeTo(displayMode: displayMode) } - } // MARK: UINavigationControllerDelegate extension SceneCoordinator: UINavigationControllerDelegate { - + func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { - - if UIApplication.shared.applicationState == .background { + guard UIApplication.shared.applicationState != .background else { return } + guard rootSplitViewController.isCollapsed else { + return + } + + defer { + if let mainController = viewController as? MainControllerIdentifiable { + lastMainControllerToAppear = mainController.mainControllerIdentifier + } else if let mainController = (viewController as? UINavigationController)?.topViewController as? MainControllerIdentifiable { + lastMainControllerToAppear = mainController.mainControllerIdentifier + } + } + // If we are showing the Feeds and only the feeds start clearing stuff - if viewController === feedsViewController && !isThreePanelMode && !isTimelineViewControllerPending { + if viewController === sidebarViewController && lastMainControllerToAppear == .mainTimeline { activityManager.invalidateCurrentActivities() selectFeed(nil, animations: [.scroll, .select, .navigation]) return @@ -1387,26 +1377,52 @@ extension SceneCoordinator: UINavigationControllerDelegate { // Don't clear it if we have pushed an ArticleViewController, but don't yet see it on the navigation stack. // This happens when we are going to the next unread and we need to grab another timeline to continue. The // ArticleViewController will be pushed, but we will briefly show the Timeline. Don't clear things out when that happens. - if viewController === timelineViewController && !isThreePanelMode && rootSplitViewController.isCollapsed && !isArticleViewControllerPending { - currentArticle = nil - timelineViewController?.updateArticleSelection(animations: [.scroll, .select, .navigation]) - activityManager.invalidateReading() + if viewController === timelineViewController && lastMainControllerToAppear == .article { + selectArticle(nil, animations: [.scroll, .select, .navigation]) // Restore any bars hidden by the article controller showStatusBar() - navigationController.setNavigationBarHidden(false, animated: true) + + // We delay the showing of the navigation bars because it freaks out on iOS 15 with the new split view controller + // if it is trying to show at the same time as the show timeline animation + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + navigationController.setNavigationBarHidden(false, animated: true) + } navigationController.setToolbarHidden(false, animated: true) return } - } - } // MARK: Private private extension SceneCoordinator { + func configureNavigationController(_ navController: UINavigationController) { + + let scrollEdge = UINavigationBarAppearance() + scrollEdge.configureWithOpaqueBackground() + scrollEdge.shadowColor = nil + scrollEdge.shadowImage = UIImage() + + let standard = UINavigationBarAppearance() + standard.shadowColor = .opaqueSeparator + standard.shadowImage = UIImage() + + navController.navigationBar.standardAppearance = standard + navController.navigationBar.compactAppearance = standard + navController.navigationBar.scrollEdgeAppearance = scrollEdge + navController.navigationBar.compactScrollEdgeAppearance = scrollEdge + + navController.navigationBar.tintColor = AppAssets.primaryAccentColor + + let toolbarAppearance = UIToolbarAppearance() + navController.toolbar.standardAppearance = toolbarAppearance + navController.toolbar.compactAppearance = toolbarAppearance + navController.toolbar.scrollEdgeAppearance = toolbarAppearance + navController.toolbar.tintColor = AppAssets.primaryAccentColor + } + func markArticlesWithUndo(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) { guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager, completion: completion) else { @@ -1429,7 +1445,7 @@ private extension SceneCoordinator { func rebuildArticleDictionaries() { var idDictionary = [String: Article]() - articles.forEach { article in + for article in articles { idDictionary[article.articleID] = article } @@ -1500,7 +1516,7 @@ private extension SceneCoordinator { updateExpandedNodes?() let changes = rebuildShadowTable() - feedsViewController.reloadFeeds(initialLoad: initialLoad, changes: changes, completion: completion) + sidebarViewController.reloadFeeds(initialLoad: initialLoad, changes: changes, completion: completion) } } @@ -1514,15 +1530,17 @@ private extension SceneCoordinator { if isExpanded(sectionNode) { for node in sectionNode.childNodes { - feedNodes.append(FeedNode(node)) + let feedNode = FeedNode(node) + feedNodes.append(feedNode) if isExpanded(node) { for child in node.childNodes { - feedNodes.append(FeedNode(child)) + let childNode = FeedNode(child) + feedNodes.append(childNode) } } } } - + let sectionID = (sectionNode.representedObject as? Account)?.accountID ?? "" newShadowTable.append((sectionID: sectionID, feedNodes: feedNodes)) } @@ -1532,9 +1550,10 @@ private extension SceneCoordinator { currentFeedIndexPath = indexPathFor(timelineFeed as AnyObject) } - // Compute the differences in the shadow table rows + // Compute the differences in the shadow table rows and the expanded table entries var changes = [ShadowTableChanges.RowChanges]() - + let expandedTableDifference = lastExpandedTable.symmetricDifference(expandedTable) + for (section, newSectionRows) in newShadowTable.enumerated() { var moves = Set() var inserts = Set() @@ -1560,9 +1579,22 @@ private extension SceneCoordinator { } } - changes.append(ShadowTableChanges.RowChanges(section: section, deletes: deletes, inserts: inserts, moves: moves)) + // We need to reload the difference in expanded rows to get the disclosure arrows correct when programmatically changing their state + var reloads = Set() + + for (index, newFeedNode) in newSectionRows.feedNodes.enumerated() { + if let newFeedNodeContainerID = (newFeedNode.node.representedObject as? Container)?.containerID { + if expandedTableDifference.contains(newFeedNodeContainerID) { + reloads.insert(index) + } + } + } + + changes.append(ShadowTableChanges.RowChanges(section: section, deletes: deletes, inserts: inserts, reloads: reloads, moves: moves)) } + lastExpandedTable = expandedTable + // Compute the difference in the shadow table sections var moves = Set() var inserts = Set() @@ -1610,13 +1642,6 @@ private extension SceneCoordinator { } } - func indexPathFor(_ object: AnyObject) -> IndexPath? { - guard let node = treeController.rootNode.descendantNodeRepresentingObject(object) else { - return nil - } - return indexPathFor(node) - } - func setTimelineFeed(_ feed: SidebarItem?, animated: Bool, completion: (() -> Void)? = nil) { timelineFeed = feed @@ -1986,9 +2011,11 @@ private extension SceneCoordinator { func fetchAndReplaceArticlesAsync(animated: Bool, completion: @escaping () -> Void) { // To be called when we need to do an entire fetch, but an async delay is okay. // Example: we have the Today feed selected, and the calendar day just changed. + cancelPendingAsyncFetches() + emptyTheTimeline() + guard let timelineFeed = timelineFeed else { - emptyTheTimeline() completion() return } @@ -2004,7 +2031,6 @@ private extension SceneCoordinator { self?.replaceArticles(with: articles, animated: animated) completion() } - } func fetchUnsortedArticlesAsync(for representedObjects: [Any], completion: @escaping ArticleSetBlock) { @@ -2060,137 +2086,9 @@ private extension SceneCoordinator { return false } - - // MARK: Three Panel Mode - - func installTimelineControllerIfNecessary(animated: Bool) { - if navControllerForTimeline().viewControllers.filter({ $0 is TimelineViewController }).count < 1 { - isTimelineViewControllerPending = true - timelineViewController = UIStoryboard.main.instantiateController(ofType: TimelineViewController.self) - timelineViewController!.coordinator = self - navControllerForTimeline().pushViewController(timelineViewController!, animated: animated) - } - } - - @discardableResult - func installArticleController(state: ArticleViewController.State? = nil, animated: Bool) -> ArticleViewController { - isArticleViewControllerPending = true - - let articleController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) - articleController.coordinator = self - articleController.article = currentArticle - articleController.restoreState = state - - if let subSplit = subSplitViewController { - let controller = addNavControllerIfNecessary(articleController, showButton: false) - subSplit.showDetailViewController(controller, sender: self) - } else if rootSplitViewController.isCollapsed || wasRootSplitViewControllerCollapsed { - navigationController.pushViewController(articleController, animated: animated) - } else { - let controller = addNavControllerIfNecessary(articleController, showButton: true) - rootSplitViewController.showDetailViewController(controller, sender: self) - } - - return articleController - - } - - func addNavControllerIfNecessary(_ controller: UIViewController, showButton: Bool) -> UIViewController { - - // You will sometimes get a compact horizontal size class while in three panel mode. Dunno why it lies. - if rootSplitViewController.traitCollection.horizontalSizeClass == .compact && !isThreePanelMode { - - return controller - - } else { - - let navController = InteractiveNavigationController.template(rootViewController: controller) - navController.isToolbarHidden = false - - if showButton { - controller.navigationItem.leftBarButtonItem = rootSplitViewController.displayModeButtonItem - controller.navigationItem.leftItemsSupplementBackButton = true - } else { - controller.navigationItem.leftBarButtonItem = nil - controller.navigationItem.leftItemsSupplementBackButton = false - } - - return navController - - } - - } - - func installSubSplit() { - rootSplitViewController.preferredPrimaryColumnWidthFraction = 0.30 - - subSplitViewController = UISplitViewController() - subSplitViewController!.preferredDisplayMode = .oneBesideSecondary - subSplitViewController!.viewControllers = [InteractiveNavigationController.template()] - subSplitViewController!.preferredPrimaryColumnWidthFraction = 0.4285 - - rootSplitViewController.showDetailViewController(subSplitViewController!, sender: self) - rootSplitViewController.setOverrideTraitCollection(UITraitCollection(horizontalSizeClass: .regular), forChild: subSplitViewController!) - } - - func navControllerForTimeline() -> UINavigationController { - if let subSplit = subSplitViewController { - return subSplit.viewControllers.first as! UINavigationController - } else { - return navigationController - } - } - - func configureThreePanelMode() { - articleViewController?.stopArticleExtractorIfProcessing() - let articleViewControllerState = articleViewController?.currentState - defer { - navigationController.viewControllers = [feedsViewController] - } - - if rootSplitViewController.viewControllers.last is InteractiveNavigationController { - _ = rootSplitViewController.viewControllers.popLast() - } - - installSubSplit() - installTimelineControllerIfNecessary(animated: false) - timelineViewController?.navigationItem.leftBarButtonItem = rootSplitViewController.displayModeButtonItem - timelineViewController?.navigationItem.leftItemsSupplementBackButton = true - - installArticleController(state: articleViewControllerState, animated: false) - - feedsViewController.restoreSelectionIfNecessary(adjustScroll: true) - timelineViewController!.restoreSelectionIfNecessary(adjustScroll: false) - } - - func configureStandardPanelMode() { - articleViewController?.stopArticleExtractorIfProcessing() - let articleViewControllerState = articleViewController?.currentState - rootSplitViewController.preferredPrimaryColumnWidthFraction = UISplitViewController.automaticDimension - - // Set the is Pending flags early to prevent the navigation controller delegate from thinking that we - // swiping around in the user interface - isTimelineViewControllerPending = true - isArticleViewControllerPending = true - - navigationController.viewControllers = [feedsViewController] - if rootSplitViewController.viewControllers.last is UISplitViewController { - subSplitViewController = nil - _ = rootSplitViewController.viewControllers.popLast() - } - - if currentFeedIndexPath != nil { - timelineViewController = UIStoryboard.main.instantiateController(ofType: TimelineViewController.self) - timelineViewController!.coordinator = self - navigationController.pushViewController(timelineViewController!, animated: false) - } - - installArticleController(state: articleViewControllerState, animated: false) - } - // MARK: NSUserActivity - + func windowState() -> [AnyHashable: Any] { let containerExpandedWindowState = expandedTable.map( { $0.userInfo }) var readArticlesFilterState = [[AnyHashable: AnyHashable]: Bool]() @@ -2200,7 +2098,8 @@ private extension SceneCoordinator { return [ UserInfoKey.readFeedsFilterState: isReadFeedsFiltered, UserInfoKey.containerExpandedWindowState: containerExpandedWindowState, - UserInfoKey.readArticlesFilterState: readArticlesFilterState + UserInfoKey.readArticlesFilterState: readArticlesFilterState, + UserInfoKey.isSidebarHidden: rootSplitViewController.displayMode == .secondaryOnly ] } @@ -2223,7 +2122,7 @@ private extension SceneCoordinator { self.treeControllerDelegate.resetFilterExceptions() if let indexPath = self.indexPathFor(smartFeed) { self.selectFeed(indexPath: indexPath) { - self.feedsViewController.focus() + self.sidebarViewController.focus() } } }) @@ -2244,7 +2143,7 @@ private extension SceneCoordinator { if let folderNode = self.findFolderNode(folderName: folderName, beginningAt: accountNode), let indexPath = self.indexPathFor(folderNode) { self.selectFeed(indexPath: indexPath) { - self.feedsViewController.focus() + self.sidebarViewController.focus() } } }) @@ -2256,8 +2155,8 @@ private extension SceneCoordinator { return } - self.discloseFeed(feed) { - self.feedsViewController.focus() + self.discloseFeed(feed, initialLoad: true) { + self.sidebarViewController.focus() } } } @@ -2281,11 +2180,11 @@ private extension SceneCoordinator { return } - guard let Feed = account.existingFeed(withFeedID: feedID) else { + guard let feed = account.existingFeed(withFeedID: feedID) else { return } - discloseFeed(Feed) { + discloseFeed(feed) { self.selectArticleInCurrentFeed(articleID) } } diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index 434ab9574..e794298ab 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -11,37 +11,38 @@ import UserNotifications import Account class SceneDelegate: UIResponder, UIWindowSceneDelegate { - + var window: UIWindow? - var coordinator = SceneCoordinator() - + var coordinator: SceneCoordinator! + // UIWindowScene delegate func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - window = UIWindow(windowScene: scene as! UIWindowScene) window!.tintColor = AppAssets.primaryAccentColor updateUserInterfaceStyle() - window!.rootViewController = coordinator.start(for: window!.frame.size) - + + let rootViewController = window!.rootViewController as! RootSplitViewController + coordinator = SceneCoordinator(rootSplitViewController: rootViewController) + rootViewController.coordinator = coordinator + rootViewController.delegate = coordinator + rootViewController.showsSecondaryOnlyButton = true + coordinator.restoreWindowState(session.stateRestorationActivity) NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil) if let _ = connectionOptions.urlContexts.first?.url { - window?.makeKeyAndVisible() self.scene(scene, openURLContexts: connectionOptions.urlContexts) return } if let shortcutItem = connectionOptions.shortcutItem { - window!.makeKeyAndVisible() handleShortcutItem(shortcutItem) return } if let notificationResponse = connectionOptions.notificationResponse { - window!.makeKeyAndVisible() coordinator.handle(notificationResponse) return } @@ -49,8 +50,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity { coordinator.handle(userActivity) } - - window!.makeKeyAndVisible() } func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { @@ -73,7 +72,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func sceneWillEnterForeground(_ scene: UIScene) { appDelegate.resumeDatabaseProcessingIfNecessary() appDelegate.prepareAccountsForForeground() - coordinator.configurePanelMode(for: window!.frame.size) coordinator.resetFocus() } @@ -96,6 +94,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { coordinator.cleanUp(conditional: conditional) } + func presentError(_ error: Error) { + self.window!.rootViewController?.presentError(error) + } + // Handle Opening of URLs func scene(_ scene: UIScene, openURLContexts urlContexts: Set) { diff --git a/iOS/Timeline/TimelineViewController.swift b/iOS/Timeline/TimelineViewController.swift index 951665e21..86faafbd1 100644 --- a/iOS/Timeline/TimelineViewController.swift +++ b/iOS/Timeline/TimelineViewController.swift @@ -116,7 +116,6 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(true) - coordinator.isTimelineViewControllerPending = false if navigationController?.navigationBar.alpha == 0 { UIView.animate(withDuration: 0.5) { @@ -605,10 +604,6 @@ private extension TimelineViewController { func configureToolbar() { - guard !coordinator.isThreePanelMode else { - return - } - guard let refreshProgressView = Bundle.main.loadNibNamed("RefreshProgressView", owner: self, options: nil)?[0] as? RefreshProgressView else { return }