diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard index 8f2a209c6..ad2c65b9e 100644 --- a/iOS/Base.lproj/Main.storyboard +++ b/iOS/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -18,7 +18,7 @@ @@ -107,14 +107,14 @@ - + - + - + @@ -153,18 +153,32 @@ - + + + + + + + + + + + + + + + - + - + @@ -216,7 +230,7 @@ - + @@ -417,7 +431,7 @@ - + diff --git a/iOS/Inspector/AccountInspectorViewController.swift b/iOS/Inspector/AccountInspectorViewController.swift index bcb707f4b..6a8366f48 100644 --- a/iOS/Inspector/AccountInspectorViewController.swift +++ b/iOS/Inspector/AccountInspectorViewController.swift @@ -18,7 +18,7 @@ class AccountInspectorViewController: UITableViewController { @IBOutlet weak var activeSwitch: UISwitch! @IBOutlet weak var deleteAccountButton: VibrantButton! @IBOutlet weak var limitationsAndSolutionsButton: UIButton! - + var isModal = false weak var account: Account? @@ -41,7 +41,7 @@ class AccountInspectorViewController: UITableViewController { if account.type != .cloudKit { limitationsAndSolutionsButton.isHidden = true } - + if isModal { let doneBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done)) navigationItem.leftBarButtonItem = doneBarButtonItem @@ -121,13 +121,12 @@ class AccountInspectorViewController: UITableViewController { present(alertController, animated: true) } - + @IBAction func openLimitationsAndSolutions(_ sender: Any) { let vc = SFSafariViewController(url: CloudKitWebDocumentation.limitationsAndSolutionsURL) vc.modalPresentationStyle = .pageSheet present(vc, animated: true) } - } // MARK: Table View diff --git a/iOS/Inspector/WebFeedInspectorViewController.swift b/iOS/Inspector/WebFeedInspectorViewController.swift new file mode 100644 index 000000000..e69de29bb diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift index 16857fc8e..b73334d1a 100644 --- a/iOS/KeyboardManager.swift +++ b/iOS/KeyboardManager.swift @@ -51,7 +51,6 @@ class KeyboardManager { keyCommand.wantsPriorityOverSystemBehavior = true return keyCommand } - } private extension KeyboardManager { @@ -60,7 +59,7 @@ private extension KeyboardManager { guard let input = createKeyCommandInput(keyEntry: keyEntry) else { return nil } let modifiers = createKeyModifierFlags(keyEntry: keyEntry) let action = keyEntry["action"] as! String - + if let title = keyEntry["title"] as? String { return KeyboardManager.createKeyCommand(title: title, action: action, input: input, modifiers: modifiers) } else { @@ -69,7 +68,7 @@ private extension KeyboardManager { return keyCommand } } - + static func createKeyCommandInput(keyEntry: [String: Any]) -> String? { guard let key = keyEntry["key"] as? String else { return nil } diff --git a/iOS/MainFeed/Cell/MainFeedTableViewCell.swift b/iOS/MainFeed/Cell/MainFeedTableViewCell.swift index 4a36e4f73..e90ba3b65 100644 --- a/iOS/MainFeed/Cell/MainFeedTableViewCell.swift +++ b/iOS/MainFeed/Cell/MainFeedTableViewCell.swift @@ -12,7 +12,7 @@ import Account import RSTree protocol MainFeedTableViewCellDelegate: AnyObject { - func feedTableViewCellDisclosureDidToggle(_ sender: MainFeedTableViewCell, expanding: Bool) + func mainFeedTableViewCellDisclosureDidToggle(_ sender: MainFeedTableViewCell, expanding: Bool) } class MainFeedTableViewCell : VibrantTableViewCell { @@ -149,7 +149,7 @@ class MainFeedTableViewCell : VibrantTableViewCell { @objc func buttonPressed(_ sender: UIButton) { if isDisclosureAvailable { setDisclosure(isExpanded: !isDisclosureExpanded, animated: true) - delegate?.feedTableViewCellDisclosureDidToggle(self, expanding: isDisclosureExpanded) + delegate?.mainFeedTableViewCellDisclosureDidToggle(self, expanding: isDisclosureExpanded) } } diff --git a/iOS/MainFeed/Cell/MainFeedTableViewSectionHeader.swift b/iOS/MainFeed/Cell/MainFeedTableViewSectionHeader.swift index f2de7af54..5041cc528 100644 --- a/iOS/MainFeed/Cell/MainFeedTableViewSectionHeader.swift +++ b/iOS/MainFeed/Cell/MainFeedTableViewSectionHeader.swift @@ -9,7 +9,7 @@ import UIKit protocol MainFeedTableViewSectionHeaderDelegate { - func feedTableViewSectionHeaderDisclosureDidToggle(_ sender: MainFeedTableViewSectionHeader) + func mainFeedTableViewSectionHeaderDisclosureDidToggle(_ sender: MainFeedTableViewSectionHeader) } class MainFeedTableViewSectionHeader: UITableViewHeaderFooterView { @@ -135,7 +135,7 @@ class MainFeedTableViewSectionHeader: UITableViewHeaderFooterView { private extension MainFeedTableViewSectionHeader { @objc func toggleDisclosure() { - delegate?.feedTableViewSectionHeaderDisclosureDidToggle(self) + delegate?.mainFeedTableViewSectionHeaderDisclosureDidToggle(self) } func commonInit() { @@ -191,10 +191,15 @@ private extension MainFeedTableViewSectionHeader { titleView.setFrameIfNotEqual(layout.titleRect) unreadCountView.setFrameIfNotEqual(layout.unreadCountRect) disclosureButton.setFrameIfNotEqual(layout.disclosureButtonRect) - - let top = CGRect(x: safeAreaInsets.left, y: 0, width: frame.width - safeAreaInsets.right - safeAreaInsets.left, height: 0.33) + + let x = -safeAreaInsets.left + let width = safeAreaInsets.left + safeAreaInsets.right + frame.width + let height = 0.33 + + let top = CGRect(x: x, y: 0, width: width, height: height) topSeparatorView.setFrameIfNotEqual(top) - let bottom = CGRect(x: safeAreaInsets.left, y: frame.height - 0.33, width: frame.width - safeAreaInsets.right - safeAreaInsets.left, height: 0.33) + + let bottom = CGRect(x: x, y: frame.height - height, width: width, height: height) bottomSeparatorView.setFrameIfNotEqual(bottom) } diff --git a/iOS/MainFeed/MainFeedViewController+Drag.swift b/iOS/MainFeed/MainFeedViewController+Drag.swift index cd3e7aef6..3defd3bb5 100644 --- a/iOS/MainFeed/MainFeedViewController+Drag.swift +++ b/iOS/MainFeed/MainFeedViewController+Drag.swift @@ -1,5 +1,5 @@ // -// FeedViewController+Drag.swift +// MainFeedViewController+Drag.swift // NetNewsWire-iOS // // Created by Maurice Parker on 11/20/19. diff --git a/iOS/MainFeed/MainFeedViewController+Drop.swift b/iOS/MainFeed/MainFeedViewController+Drop.swift index 01a466ccf..7507dd162 100644 --- a/iOS/MainFeed/MainFeedViewController+Drop.swift +++ b/iOS/MainFeed/MainFeedViewController+Drop.swift @@ -1,5 +1,5 @@ // -// FeedViewController+Drop.swift +// MainFeedViewController+Drop.swift // NetNewsWire-iOS // // Created by Maurice Parker on 11/20/19. diff --git a/iOS/MainFeed/MainFeedViewController.swift b/iOS/MainFeed/MainFeedViewController.swift index 56ff17fb2..95e49604d 100644 --- a/iOS/MainFeed/MainFeedViewController.swift +++ b/iOS/MainFeed/MainFeedViewController.swift @@ -68,17 +68,19 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) registerForTraitChanges([UITraitPreferredContentSizeCategory.self], target: self, action: #selector(preferredContentSizeCategoryDidChange)) refreshControl = UIRefreshControl() refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged) - + configureToolbar() becomeFirstResponder() } override func viewWillAppear(_ animated: Bool) { + navigationController?.isToolbarHidden = false updateUI() super.viewWillAppear(animated) } @@ -90,6 +92,16 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { reloadAllVisibleCells() } + private func headerViewForAccount(_ account: Account) -> MainFeedTableViewSectionHeader? { + + guard let node = coordinator.rootNode.childNodeRepresentingObject(account), + let sectionIndex = coordinator.rootNode.indexOfChild(node) else { + return nil + } + + return tableView.headerView(forSection: sectionIndex) as? MainFeedTableViewSectionHeader + } + @objc func unreadCountDidChange(_ note: Notification) { updateUI() @@ -98,15 +110,12 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { } if let account = unreadCountProvider as? Account { - if let node = coordinator.rootNode.childNodeRepresentingObject(account) { - let sectionIndex = coordinator.rootNode.indexOfChild(node)! - if let headerView = tableView.headerView(forSection: sectionIndex) as? MainFeedTableViewSectionHeader { - headerView.unreadCount = account.unreadCount - } + if let headerView = headerViewForAccount(account) { + headerView.unreadCount = account.unreadCount } return } - + var node: Node? = nil if let coordinator = unreadCountProvider as? SceneCoordinator, let feed = coordinator.timelineFeed { node = coordinator.rootNode.descendantNodeRepresentingObject(feed as AnyObject) @@ -139,7 +148,21 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { configureCellsForRepresentedObject(feed) } } - + + @objc func displayNameDidChange(_ note: Notification) { + + if let account = note.object as? Account { + if let headerView = headerViewForAccount(account) { + headerView.name = account.nameForDisplay + } + return + } + + if let representedObject = note.object as? AnyObject { + configureCellsForRepresentedObject(representedObject) + } + } + @objc func contentSizeCategoryDidChange(_ note: Notification) { resetEstimatedRowHeight() tableView.reloadData() @@ -473,7 +496,7 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { } return super.canPerformAction(action, withSender: sender) } - + @objc func expandSelectedRows(_ sender: Any?) { if let indexPath = coordinator.currentFeedIndexPath, let node = coordinator.nodeFor(indexPath) { coordinator.expand(node) @@ -512,7 +535,7 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { // MARK: API func restoreSelectionIfNecessary(adjustScroll: Bool) { - if let indexPath = coordinator.feedIndexPathForCurrentTimeline() { + if let indexPath = coordinator.mainFeedIndexPathForCurrentTimeline() { if adjustScroll { tableView.selectRowAndScrollIfNotVisible(at: indexPath, animations: []) } else { @@ -680,21 +703,21 @@ extension MainFeedViewController: UIContextMenuInteractionDelegate { } } -// MARK: FeedTableViewSectionHeaderDelegate +// MARK: MainFeedTableViewSectionHeaderDelegate extension MainFeedViewController: MainFeedTableViewSectionHeaderDelegate { - - func feedTableViewSectionHeaderDisclosureDidToggle(_ sender: MainFeedTableViewSectionHeader) { + + func mainFeedTableViewSectionHeaderDisclosureDidToggle(_ sender: MainFeedTableViewSectionHeader) { toggle(sender) } } -// MARK: FeedTableViewCellDelegate +// MARK: MainTableViewCellDelegate extension MainFeedViewController: MainFeedTableViewCellDelegate { - - func feedTableViewCellDisclosureDidToggle(_ sender: MainFeedTableViewCell, expanding: Bool) { + + func mainFeedTableViewCellDisclosureDidToggle(_ sender: MainFeedTableViewCell, expanding: Bool) { if expanding { expand(sender) } else { diff --git a/iOS/MainFeed/RefreshProgressView.swift b/iOS/MainFeed/RefreshProgressView.swift index 6c20ce95d..937ab8345 100644 --- a/iOS/MainFeed/RefreshProgressView.swift +++ b/iOS/MainFeed/RefreshProgressView.swift @@ -15,7 +15,8 @@ class RefreshProgressView: UIView { @IBOutlet weak var label: UILabel! override func awakeFromNib() { - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .combinedRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) update() scheduleUpdateRefreshLabel() diff --git a/iOS/MainFeed/ShadowTableChanges.swift b/iOS/MainFeed/ShadowTableChanges.swift index 49c4a9f9f..ad5e9a73f 100644 --- a/iOS/MainFeed/ShadowTableChanges.swift +++ b/iOS/MainFeed/ShadowTableChanges.swift @@ -9,11 +9,11 @@ import Foundation struct ShadowTableChanges { - + struct Move: Hashable { var from: Int var to: Int - + init(_ from: Int, _ to: Int) { self.from = from self.to = to @@ -21,37 +21,37 @@ struct ShadowTableChanges { } struct RowChanges { - + var section: Int var deletes: Set? var inserts: Set? var reloads: Set? var moves: Set? - + var isEmpty: Bool { return (deletes?.isEmpty ?? true) && (inserts?.isEmpty ?? true) && (moves?.isEmpty ?? true) } - + var deleteIndexPaths: [IndexPath]? { guard let deletes = deletes else { return nil } return deletes.map { IndexPath(row: $0, section: section) } } - + var insertIndexPaths: [IndexPath]? { guard let inserts = inserts else { return nil } 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?, reloads: Set?, moves: Set?) { self.section = section self.deletes = deletes @@ -59,7 +59,6 @@ struct ShadowTableChanges { self.reloads = reloads self.moves = moves } - } var deletes: Set? @@ -73,5 +72,4 @@ struct ShadowTableChanges { self.moves = moves self.rowChanges = rowChanges } - } diff --git a/iOS/MainTimeline/Cell/MainTimelineAccessibilityCellLayout.swift b/iOS/MainTimeline/Cell/MainTimelineAccessibilityCellLayout.swift index 7d818940f..c72345ae1 100644 --- a/iOS/MainTimeline/Cell/MainTimelineAccessibilityCellLayout.swift +++ b/iOS/MainTimeline/Cell/MainTimelineAccessibilityCellLayout.swift @@ -10,7 +10,7 @@ import UIKit import RSCore struct MainTimelineAccessibilityCellLayout: MainTimelineCellLayout { - + let height: CGFloat let unreadIndicatorRect: CGRect let starRect: CGRect @@ -21,18 +21,18 @@ struct MainTimelineAccessibilityCellLayout: MainTimelineCellLayout { let dateRect: CGRect init(width: CGFloat, insets: UIEdgeInsets, cellData: MainTimelineCellData) { - + var currentPoint = CGPoint.zero currentPoint.x = MainTimelineDefaultCellLayout.cellPadding.left + insets.left + MainTimelineDefaultCellLayout.unreadCircleMarginLeft currentPoint.y = MainTimelineDefaultCellLayout.cellPadding.top - + // Unread Indicator and Star self.unreadIndicatorRect = MainTimelineAccessibilityCellLayout.rectForUnreadIndicator(currentPoint) self.starRect = MainTimelineAccessibilityCellLayout.rectForStar(currentPoint) - + // Start the point at the beginning position of the main block currentPoint.x += MainTimelineDefaultCellLayout.unreadCircleDimension + MainTimelineDefaultCellLayout.unreadCircleMarginRight - + // Icon Image if cellData.showIcon { self.iconImageRect = MainTimelineAccessibilityCellLayout.rectForIconView(currentPoint, iconSize: cellData.iconSize) @@ -42,7 +42,7 @@ struct MainTimelineAccessibilityCellLayout: MainTimelineCellLayout { } let textAreaWidth = width - (currentPoint.x + MainTimelineDefaultCellLayout.cellPadding.right + insets.right) - + // Title Text Block let (titleRect, numberOfLinesForTitle) = MainTimelineAccessibilityCellLayout.rectForTitle(cellData, currentPoint, textAreaWidth) self.titleRect = titleRect @@ -52,7 +52,7 @@ struct MainTimelineAccessibilityCellLayout: MainTimelineCellLayout { currentPoint.y = self.titleRect.maxY + MainTimelineDefaultCellLayout.titleBottomMargin } self.summaryRect = MainTimelineAccessibilityCellLayout.rectForSummary(cellData, currentPoint, textAreaWidth, numberOfLinesForTitle) - + currentPoint.y = [self.titleRect, self.summaryRect].maxY() if cellData.showFeedName != .none { @@ -64,9 +64,9 @@ struct MainTimelineAccessibilityCellLayout: MainTimelineCellLayout { // Feed Name and Pub Date self.dateRect = MainTimelineAccessibilityCellLayout.rectForDate(cellData, currentPoint, textAreaWidth) - + self.height = self.dateRect.maxY + MainTimelineDefaultCellLayout.cellPadding.bottom - + } } @@ -74,9 +74,9 @@ struct MainTimelineAccessibilityCellLayout: MainTimelineCellLayout { // MARK: - Calculate Rects private extension MainTimelineAccessibilityCellLayout { - + static func rectForDate(_ cellData: MainTimelineCellData, _ point: CGPoint, _ textAreaWidth: CGFloat) -> CGRect { - + var r = CGRect.zero let size = SingleLineUILabelSizer.size(for: cellData.dateString, font: MainTimelineDefaultCellLayout.dateFont) diff --git a/iOS/MainTimeline/Cell/MainTimelineCellData.swift b/iOS/MainTimeline/Cell/MainTimelineCellData.swift index 1fa0bf757..397f60409 100644 --- a/iOS/MainTimeline/Cell/MainTimelineCellData.swift +++ b/iOS/MainTimeline/Cell/MainTimelineCellData.swift @@ -22,13 +22,12 @@ struct MainTimelineCellData { let showFeedName: ShowFeedName let iconImage: IconImage? // feed icon, user avatar, or favicon let showIcon: Bool // Make space even when icon is nil - let featuredImage: UIImage? // image from within the article let read: Bool let starred: Bool let numberOfLines: Int let iconSize: IconSize - init(article: Article, showFeedName: ShowFeedName, feedName: String?, byline: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: UIImage?, numberOfLines: Int, iconSize: IconSize) { + init(article: Article, showFeedName: ShowFeedName, feedName: String?, byline: String?, iconImage: IconImage?, showIcon: Bool, numberOfLines: Int, iconSize: IconSize) { self.title = ArticleStringFormatter.truncatedTitle(article) self.attributedTitle = ArticleStringFormatter.attributedTruncatedTitle(article) @@ -59,8 +58,7 @@ struct MainTimelineCellData { self.showIcon = showIcon self.iconImage = iconImage - self.featuredImage = featuredImage - + self.read = article.status.read self.starred = article.status.starred self.numberOfLines = numberOfLines @@ -78,7 +76,6 @@ struct MainTimelineCellData { self.showFeedName = .none self.showIcon = false self.iconImage = nil - self.featuredImage = nil self.read = true self.starred = false self.numberOfLines = 0 diff --git a/iOS/MainTimeline/Cell/MainTimelineCellLayout.swift b/iOS/MainTimeline/Cell/MainTimelineCellLayout.swift index 7031e1297..5d8ba6b69 100644 --- a/iOS/MainTimeline/Cell/MainTimelineCellLayout.swift +++ b/iOS/MainTimeline/Cell/MainTimelineCellLayout.swift @@ -9,7 +9,7 @@ import UIKit protocol MainTimelineCellLayout { - + var height: CGFloat {get} var unreadIndicatorRect: CGRect {get} var starRect: CGRect {get} @@ -22,7 +22,7 @@ protocol MainTimelineCellLayout { } extension MainTimelineCellLayout { - + static func rectForUnreadIndicator(_ point: CGPoint) -> CGRect { var r = CGRect.zero r.size = CGSize(width: MainTimelineDefaultCellLayout.unreadCircleDimension, height: MainTimelineDefaultCellLayout.unreadCircleDimension) @@ -50,7 +50,7 @@ extension MainTimelineCellLayout { } static func rectForTitle(_ cellData: MainTimelineCellData, _ point: CGPoint, _ textAreaWidth: CGFloat) -> (CGRect, Int) { - + var r = CGRect.zero if cellData.title.isEmpty { return (r, 0) @@ -59,7 +59,7 @@ extension MainTimelineCellLayout { r.origin = point let sizeInfo = MultilineUILabelSizer.size(for: cellData.title, font: MainTimelineDefaultCellLayout.titleFont, numberOfLines: cellData.numberOfLines, width: Int(textAreaWidth)) - + r.size.width = textAreaWidth r.size.height = sizeInfo.size.height if sizeInfo.numberOfLinesUsed < 1 { @@ -71,7 +71,7 @@ extension MainTimelineCellLayout { } static func rectForSummary(_ cellData: MainTimelineCellData, _ point: CGPoint, _ textAreaWidth: CGFloat, _ linesUsed: Int) -> CGRect { - + let linesLeft = cellData.numberOfLines - linesUsed var r = CGRect.zero @@ -82,7 +82,7 @@ extension MainTimelineCellLayout { r.origin = point let sizeInfo = MultilineUILabelSizer.size(for: cellData.summary, font: MainTimelineDefaultCellLayout.summaryFont, numberOfLines: linesLeft, width: Int(textAreaWidth)) - + r.size.width = textAreaWidth r.size.height = sizeInfo.size.height if sizeInfo.numberOfLinesUsed < 1 { @@ -94,7 +94,7 @@ extension MainTimelineCellLayout { } static func rectForFeedName(_ cellData: MainTimelineCellData, _ point: CGPoint, _ textAreaWidth: CGFloat) -> CGRect { - + var r = CGRect.zero r.origin = point diff --git a/iOS/MainTimeline/Cell/MainTimelineDefaultCellLayout.swift b/iOS/MainTimeline/Cell/MainTimelineDefaultCellLayout.swift index 388019be4..e85f43ef7 100644 --- a/iOS/MainTimeline/Cell/MainTimelineDefaultCellLayout.swift +++ b/iOS/MainTimeline/Cell/MainTimelineDefaultCellLayout.swift @@ -1,5 +1,5 @@ // -// TimelineDefaultCellLayout.swift +// MainTimelineDefaultCellLayout.swift // NetNewsWire // // Created by Brent Simmons on 2/6/16. @@ -57,14 +57,14 @@ struct MainTimelineDefaultCellLayout: MainTimelineCellLayout { var currentPoint = CGPoint.zero currentPoint.x = MainTimelineDefaultCellLayout.cellPadding.left + insets.left + MainTimelineDefaultCellLayout.unreadCircleMarginLeft currentPoint.y = MainTimelineDefaultCellLayout.cellPadding.top - + // Unread Indicator and Star self.unreadIndicatorRect = MainTimelineDefaultCellLayout.rectForUnreadIndicator(currentPoint) self.starRect = MainTimelineDefaultCellLayout.rectForStar(currentPoint) - + // Start the point at the beginning position of the main block currentPoint.x += MainTimelineDefaultCellLayout.unreadCircleDimension + MainTimelineDefaultCellLayout.unreadCircleMarginRight - + // Icon Image if cellData.showIcon { self.iconImageRect = MainTimelineDefaultCellLayout.rectForIconView(currentPoint, iconSize: cellData.iconSize) @@ -74,7 +74,7 @@ struct MainTimelineDefaultCellLayout: MainTimelineCellLayout { } let textAreaWidth = width - (currentPoint.x + MainTimelineDefaultCellLayout.cellPadding.right + insets.right) - + // Title Text Block let (titleRect, numberOfLinesForTitle) = MainTimelineDefaultCellLayout.rectForTitle(cellData, currentPoint, textAreaWidth) self.titleRect = titleRect @@ -84,7 +84,7 @@ struct MainTimelineDefaultCellLayout: MainTimelineCellLayout { currentPoint.y = self.titleRect.maxY + MainTimelineDefaultCellLayout.titleBottomMargin } self.summaryRect = MainTimelineDefaultCellLayout.rectForSummary(cellData, currentPoint, textAreaWidth, numberOfLinesForTitle) - + var y = [self.titleRect, self.summaryRect].maxY() if y == 0 { y = iconImageRect.origin.y + iconImageRect.height @@ -96,10 +96,10 @@ struct MainTimelineDefaultCellLayout: MainTimelineCellLayout { // Feed Name and Pub Date self.dateRect = MainTimelineDefaultCellLayout.rectForDate(cellData, currentPoint, textAreaWidth) - + let feedNameWidth = textAreaWidth - (MainTimelineDefaultCellLayout.feedRightMargin + self.dateRect.size.width) self.feedNameRect = MainTimelineDefaultCellLayout.rectForFeedName(cellData, currentPoint, feedNameWidth) - + self.height = [self.iconImageRect, self.feedNameRect].maxY() + MainTimelineDefaultCellLayout.cellPadding.bottom } @@ -111,7 +111,7 @@ struct MainTimelineDefaultCellLayout: MainTimelineCellLayout { extension MainTimelineDefaultCellLayout { static func rectForDate(_ cellData: MainTimelineCellData, _ point: CGPoint, _ textAreaWidth: CGFloat) -> CGRect { - + var r = CGRect.zero let size = SingleLineUILabelSizer.size(for: cellData.dateString, font: MainTimelineDefaultCellLayout.dateFont) diff --git a/iOS/MainTimeline/Cell/MainTimelineTableViewCell.swift b/iOS/MainTimeline/Cell/MainTimelineTableViewCell.swift index 4b5542082..f508029b0 100644 --- a/iOS/MainTimeline/Cell/MainTimelineTableViewCell.swift +++ b/iOS/MainTimeline/Cell/MainTimelineTableViewCell.swift @@ -10,13 +10,13 @@ import UIKit import RSCore class MainTimelineTableViewCell: VibrantTableViewCell { - + private let titleView = MainTimelineTableViewCell.multiLineUILabel() private let summaryView = MainTimelineTableViewCell.multiLineUILabel() private let unreadIndicatorView = MainUnreadIndicatorView(frame: CGRect.zero) private let dateView = MainTimelineTableViewCell.singleLineUILabel() private let feedNameView = MainTimelineTableViewCell.singleLineUILabel() - + private lazy var iconView = IconView() private lazy var starView = { @@ -107,7 +107,7 @@ class MainTimelineTableViewCell: VibrantTableViewCell { // MARK: - Private private extension MainTimelineTableViewCell { - + static func singleLineUILabel() -> UILabel { let label = NonIntrinsicLabel() label.lineBreakMode = .byTruncatingTail diff --git a/iOS/MainTimeline/MainTimelineDataSource.swift b/iOS/MainTimeline/MainTimelineDataSource.swift index 310e16083..f5c6d2b85 100644 --- a/iOS/MainTimeline/MainTimelineDataSource.swift +++ b/iOS/MainTimeline/MainTimelineDataSource.swift @@ -9,7 +9,7 @@ import UIKit class MainTimelineDataSource: UITableViewDiffableDataSource where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable { - + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true } diff --git a/iOS/MainTimeline/MainTimelineViewController.swift b/iOS/MainTimeline/MainTimelineViewController.swift index db926db0c..99589a4b9 100644 --- a/iOS/MainTimeline/MainTimelineViewController.swift +++ b/iOS/MainTimeline/MainTimelineViewController.swift @@ -85,7 +85,7 @@ class MainTimelineViewController: UITableViewController, UndoableCommandRunner { iconSize = AppDefaults.shared.timelineIconSize resetEstimatedRowHeight() - if let titleView = Bundle.main.loadNibNamed("TimelineTitleView", owner: self, options: nil)?[0] as? MainTimelineTitleView { + if let titleView = Bundle.main.loadNibNamed("MainTimelineTitleView", owner: self, options: nil)?[0] as? MainTimelineTitleView { navigationItem.titleView = titleView } @@ -110,10 +110,14 @@ class MainTimelineViewController: UITableViewController, UndoableCommandRunner { } override func viewWillAppear(_ animated: Bool) { + self.navigationController?.isToolbarHidden = false + // If the nav bar is hidden, fade it in to avoid it showing stuff as it is getting laid out if navigationController?.navigationBar.isHidden ?? false { navigationController?.navigationBar.alpha = 0 } + + super.viewWillAppear(animated) } override func viewDidAppear(_ animated: Bool) { @@ -531,7 +535,7 @@ class MainTimelineViewController: UITableViewController, UndoableCommandRunner { let visibleArticles = tableView.indexPathsForVisibleRows!.compactMap { return dataSource.itemIdentifier(for: $0) } reloadCells(visibleArticles) } - + private func reloadCells(_ articles: [Article]) { var snapshot = dataSource.snapshot() snapshot.reloadItems(articles) @@ -598,9 +602,8 @@ extension MainTimelineViewController: UISearchBarDelegate { private extension MainTimelineViewController { - func configureToolbar() { - - guard !coordinator.isThreePanelMode else { + func configureToolbar() { + guard !(splitViewController?.isCollapsed ?? true) else { return } @@ -722,10 +725,9 @@ private extension MainTimelineViewController { } func configure(_ cell: MainTimelineTableViewCell, article: Article) { - + let iconImage = iconImageFor(article) - let featuredImage = featuredImageFor(article) - + let showFeedNames = coordinator.showFeedNames let showIcon = coordinator.showIcons && iconImage != nil cell.cellData = MainTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, byline: article.byline(), iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize) @@ -739,13 +741,6 @@ private extension MainTimelineViewController { return article.iconImage() } - func featuredImageFor(_ article: Article) -> UIImage? { - if let link = article.imageLink, let data = appDelegate.imageDownloader.image(for: link) { - return RSImage(data: data) - } - return nil - } - func toggleArticleReadStatusAction(_ article: Article) -> UIAction? { guard !article.status.read || article.isAvailableToMarkUnread else { return nil } @@ -842,7 +837,7 @@ private extension MainTimelineViewController { } return action } - + func discloseFeedAction(_ article: Article) -> UIAction? { guard let feed = article.feed, !coordinator.timelineFeedIsEqualTo(feed) else { return nil } diff --git a/iOS/Resources/NetNewsWire-iOS-Bridging-Header.h b/iOS/Resources/NetNewsWire-iOS-Bridging-Header.h index f84fb5400..e11d920b1 100644 --- a/iOS/Resources/NetNewsWire-iOS-Bridging-Header.h +++ b/iOS/Resources/NetNewsWire-iOS-Bridging-Header.h @@ -1,5 +1,3 @@ // // Use this file to import your target's public headers that you would like to expose to Swift. // - - diff --git a/iOS/Resources/PrivacyInfo.xcprivacy b/iOS/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..41686685c --- /dev/null +++ b/iOS/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,40 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + 3B52.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + + diff --git a/iOS/Resources/page.html b/iOS/Resources/page.html index 686c4612d..7de2264a5 100644 --- a/iOS/Resources/page.html +++ b/iOS/Resources/page.html @@ -20,3 +20,4 @@ [[body]] + diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 073aa69fb..cc7255e2a 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -25,9 +25,9 @@ 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 diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index fe71033cc..05aef192e 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -14,12 +14,6 @@ import RSCore import RSTree import SafariServices -enum PanelMode { - case unset - case three - case standard -} - enum SearchScope: Int { case timeline = 0 case global = 1 @@ -54,45 +48,25 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { lazy var webViewProvider = WebViewProvider(coordinator: self) - private var panelMode: PanelMode = .unset - private var activityManager = ActivityManager() private var rootSplitViewController: RootSplitViewController! - private var navigationController: UINavigationController! - private var feedViewController: MainFeedViewController! - private var timelineViewController: MainTimelineViewController? - 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 mainFeedViewController: MainFeedViewController! + private var mainTimelineViewController: MainTimelineViewController? + private var articleViewController: ArticleViewController? 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]() @@ -144,14 +118,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 } @@ -302,11 +274,24 @@ 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.mainFeedViewController = rootSplitViewController.viewController(for: .primary) as? MainFeedViewController + self.mainFeedViewController.coordinator = self + self.mainFeedViewController?.navigationController?.delegate = self + + self.mainTimelineViewController = rootSplitViewController.viewController(for: .supplementary) as? MainTimelineViewController + self.mainTimelineViewController?.coordinator = self + self.mainTimelineViewController?.navigationController?.delegate = self + + self.articleViewController = rootSplitViewController.viewController(for: .secondary) as? ArticleViewController + self.articleViewController?.coordinator = self + self.articleViewController?.navigationController?.delegate = self + for sectionNode in treeController.rootNode.childNodes { markExpanded(sectionNode) shadowTable.append((sectionID: "", feedNodes: [FeedNode]())) @@ -329,30 +314,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 - - feedViewController = UIStoryboard.main.instantiateController(ofType: MainFeedViewController.self) - feedViewController.coordinator = self - navigationController.pushViewController(feedViewController, 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] { @@ -407,31 +368,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() + mainTimelineViewController?.focus() } else { - feedViewController?.focus() + mainFeedViewController?.focus() } } @@ -446,9 +387,9 @@ 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() + self.mainTimelineViewController!.showSearchAll() } } } @@ -481,7 +422,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { @objc func containerChildrenDidChange(_ note: Notification) { if timelineFetcherContainsAnyPseudoFeed() || timelineFetcherContainsAnyFolder() { fetchAndMergeArticlesAsync(animated: true) { - self.timelineViewController?.reinitializeArticles(resetScroll: false) + self.mainTimelineViewController?.reinitializeArticles(resetScroll: false) self.rebuildBackingStores() } } else { @@ -500,7 +441,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { @objc func accountStateDidChange(_ note: Notification) { if timelineFetcherContainsAnyPseudoFeed() { fetchAndMergeArticlesAsync(animated: true) { - self.timelineViewController?.reinitializeArticles(resetScroll: false) + self.mainTimelineViewController?.reinitializeArticles(resetScroll: false) self.rebuildBackingStores() } } else { @@ -518,7 +459,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { if timelineFetcherContainsAnyPseudoFeed() { fetchAndMergeArticlesAsync(animated: true) { - self.timelineViewController?.reinitializeArticles(resetScroll: false) + self.mainTimelineViewController?.reinitializeArticles(resetScroll: false) self.rebuildBackingStores(updateExpandedNodes: expandNewAccount) } } else { @@ -536,7 +477,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { if timelineFetcherContainsAnyPseudoFeed() { fetchAndMergeArticlesAsync(animated: true) { - self.timelineViewController?.reinitializeArticles(resetScroll: false) + self.mainTimelineViewController?.reinitializeArticles(resetScroll: false) self.rebuildBackingStores(updateExpandedNodes: cleanupAccount) } } else { @@ -621,7 +562,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { treeControllerDelegate.isReadFiltered = true } rebuildBackingStores() - feedViewController?.updateUI() + mainFeedViewController?.updateUI() } func toggleReadArticlesFilter() { @@ -665,7 +606,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } return shadowTable[indexPath.section].feedNodes[indexPath.row].node } - + func indexPathFor(_ node: Node) -> IndexPath? { for i in 0.. IndexPath? { + func mainFeedIndexPathForCurrentTimeline() -> IndexPath? { guard let node = treeController.rootNode.descendantNodeRepresentingObject(timelineFeed as AnyObject) else { return nil } @@ -801,7 +742,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } currentFeedIndexPath = indexPath - feedViewController.updateFeedSelection(animations: animations) + mainFeedViewController.updateFeedSelection(animations: animations) if deselectArticle { selectArticle(nil) @@ -810,7 +751,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { if let ip = indexPath, let node = nodeFor(ip), let feed = node.representedObject as? SidebarItem { self.activityManager.selecting(feed: feed) - self.installTimelineControllerIfNecessary(animated: animations.contains(.navigation)) + self.rootSplitViewController.show(.supplementary) setTimelineFeed(feed, animated: false) { if self.isReadFeedsFiltered { self.rebuildBackingStores() @@ -825,9 +766,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { self.rebuildBackingStores() } self.activityManager.invalidateSelecting() - if self.rootSplitViewController.isCollapsed && self.navControllerForTimeline().viewControllers.last is MainTimelineViewController { - self.navControllerForTimeline().popViewController(animated: animations.contains(.navigation)) - } + self.rootSplitViewController.show(.primary) completion?() } @@ -875,31 +814,20 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { activityManager.reading(feed: timelineFeed, article: article) if article == nil { - if rootSplitViewController.isCollapsed { - if navigationController.children.last is ArticleViewController { - navigationController.popViewController(animated: animations.contains(.navigation)) - } - } else { - articleViewController?.article = nil - } - timelineViewController?.updateArticleSelection(animations: animations) + rootSplitViewController.show(.supplementary) + mainTimelineViewController?.updateArticleSelection(animations: animations) return } - let currentArticleViewController: ArticleViewController - if articleViewController == nil { - currentArticleViewController = installArticleController(animated: animations.contains(.navigation)) - } else { - currentArticleViewController = articleViewController! - } + rootSplitViewController.show(.secondary) // Mark article as read before navigating to it, so the read status does not flash unread/read on display markArticles(Set([article!]), statusKey: .read, flag: true) - timelineViewController?.updateArticleSelection(animations: animations) - currentArticleViewController.article = article + mainTimelineViewController?.updateArticleSelection(animations: animations) + articleViewController?.article = article if let isShowingExtractedArticle = isShowingExtractedArticle, let articleWindowScrollY = articleWindowScrollY { - currentArticleViewController.restoreScrollPosition = (isShowingExtractedArticle, articleWindowScrollY) + articleViewController?.restoreScrollPosition = (isShowingExtractedArticle, articleWindowScrollY) } } @@ -916,7 +844,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { if let oldTimelineFeed = preSearchTimelineFeed { emptyTheTimeline() timelineFeed = oldTimelineFeed - timelineViewController?.reinitializeArticles(resetScroll: true) + mainTimelineViewController?.reinitializeArticles(resetScroll: true) replaceArticles(with: savedSearchArticles!, animated: true) } else { setTimelineFeed(nil, animated: true) @@ -929,7 +857,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { savedSearchArticles = nil isSearching = false selectArticle(nil) - timelineViewController?.focus() + mainTimelineViewController?.focus() } func searchArticles(_ searchString: String, _ searchScope: SearchScope) { @@ -1012,18 +940,22 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { return } + isNavigationDisabled = true + defer { + isNavigationDisabled = false + } + if selectNextUnreadArticleInTimeline() { return } if self.isSearching { - self.timelineViewController?.hideSearch() + self.mainTimelineViewController?.hideSearch() } selectNextUnreadFeed() { self.selectNextUnreadArticleInTimeline() } - } func scrollOrGoToNextUnread() { @@ -1046,7 +978,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { func markAllAsReadInTimeline(completion: (() -> Void)? = nil) { markAllAsRead(articles) { - self.navigationController.popViewController(animated: true) + self.rootSplitViewController.show(.primary) completion?() } } @@ -1130,7 +1062,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { func discloseFeed(_ feed: Feed, initialLoad: Bool = false, animations: Animations = [], completion: (() -> Void)? = nil) { if isSearching { - timelineViewController?.hideSearch() + mainTimelineViewController?.hideSearch() } guard let account = feed.account else { @@ -1155,12 +1087,17 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { rebuildBackingStores(initialLoad: initialLoad, completion: { self.treeControllerDelegate.resetFilterExceptions() self.selectFeed(nil) { - self.selectFeed(feed, animations: animations, completion: completion) + if self.rootSplitViewController.traitCollection.horizontalSizeClass == .compact { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.selectFeed(webFeed, animations: animations, completion: completion) + } + } else { + self.selectFeed(webFeed, animations: animations, completion: completion) + } } }) - } - + func showStatusBar() { prefersStatusBarHidden = false UIView.animate(withDuration: 0.15) { @@ -1227,14 +1164,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { addNavViewController.modalPresentationStyle = .formSheet addNavViewController.preferredContentSize = AddFeedViewController.preferredContentSizeForFormSheetDisplay - feedViewController.present(addNavViewController, animated: true) + mainFeedViewController.present(addNavViewController, animated: true) } func showAddFolder() { let addNavViewController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddFolderViewControllerNav") as! UINavigationController addNavViewController.modalPresentationStyle = .formSheet addNavViewController.preferredContentSize = AddFolderViewController.preferredContentSizeForFormSheetDisplay - feedViewController.present(addNavViewController, animated: true) + mainFeedViewController.present(addNavViewController, animated: true) } func showFullScreenImage(image: UIImage, imageTitle: String?, transitioningDelegate: UIViewControllerTransitioningDelegate) { @@ -1283,12 +1220,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { articleViewController?.openInAppBrowser() } else { - feedViewController.openInAppBrowser() + mainFeedViewController.openInAppBrowser() } } func navigateToFeeds() { - feedViewController?.focus() + mainFeedViewController?.focus() selectArticle(nil) } @@ -1296,7 +1233,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { if currentArticle == nil && articles.count > 0 { selectArticle(articles[0]) } - timelineViewController?.focus() + mainTimelineViewController?.focus() } func navigateToDetail() { @@ -1315,7 +1252,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { func importTheme(filename: String) { do { - try ArticleThemeImporter.importTheme(controller: rootSplitViewController, filename: filename) + try ArticleThemeImporter.importTheme(controller: rootSplitViewController, url: URL(fileURLWithPath: filename)) } catch { NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error]) } @@ -1330,7 +1267,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { /// `SFSafariViewController` or `SettingsViewController`, /// otherwise, this function does nothing. func dismissIfLaunchingFromExternalAction() { - guard let presentedController = feedViewController.presentedViewController else { return } + guard let presentedController = mainFeedViewController.presentedViewController else { return } if presentedController.isKind(of: SFSafariViewController.self) { presentedController.dismiss(animated: true, completion: nil) @@ -1344,45 +1281,28 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { // 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) - } - } - - return true - } - - func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? { - timelineViewController?.updateUI() - guard !isThreePanelMode else { - return subSplitViewController + 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 } - - 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 } } @@ -1392,13 +1312,16 @@ extension SceneCoordinator: UISplitViewControllerDelegate { extension SceneCoordinator: UINavigationControllerDelegate { func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { + guard UIApplication.shared.applicationState != .background else { + return + } - if UIApplication.shared.applicationState == .background { + guard rootSplitViewController.isCollapsed else { return } // If we are showing the Feeds and only the feeds start clearing stuff - if viewController === feedViewController && !isThreePanelMode && !isTimelineViewControllerPending { + if viewController === mainFeedViewController && !isTimelineViewControllerPending { activityManager.invalidateCurrentActivities() selectFeed(nil, animations: [.scroll, .select, .navigation]) return @@ -1408,9 +1331,9 @@ 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 { + if viewController === mainTimelineViewController && rootSplitViewController.isCollapsed && !isArticleViewControllerPending { currentArticle = nil - timelineViewController?.updateArticleSelection(animations: [.scroll, .select, .navigation]) + mainTimelineViewController?.updateArticleSelection(animations: [.scroll, .select, .navigation]) activityManager.invalidateReading() // Restore any bars hidden by the article controller @@ -1419,9 +1342,8 @@ extension SceneCoordinator: UINavigationControllerDelegate { navigationController.setToolbarHidden(false, animated: true) return } - } - + } // MARK: Private @@ -1521,7 +1443,7 @@ private extension SceneCoordinator { updateExpandedNodes?() let changes = rebuildShadowTable() - feedViewController.reloadFeeds(initialLoad: initialLoad, changes: changes, completion: completion) + mainFeedViewController.reloadFeeds(initialLoad: initialLoad, changes: changes, completion: completion) } } @@ -1529,10 +1451,10 @@ private extension SceneCoordinator { var newShadowTable = [(sectionID: String, feedNodes: [FeedNode])]() for i in 0..() var inserts = Set() var deletes = Set() - + let oldFeedNodes = shadowTable.first(where: { $0.sectionID == newSectionRows.sectionID })?.feedNodes ?? [FeedNode]() - + let diff = newSectionRows.feedNodes.difference(from: oldFeedNodes).inferringMoves() for change in diff { switch change { @@ -1581,10 +1503,10 @@ private extension SceneCoordinator { } } } - + // 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) { @@ -1592,17 +1514,17 @@ private extension SceneCoordinator { } } } - + 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() var deletes = Set() - + let oldSections = shadowTable.map { $0.sectionID } let newSections = newShadowTable.map { $0.sectionID } let diff = newSections.difference(from: oldSections).inferringMoves() @@ -1624,7 +1546,7 @@ private extension SceneCoordinator { } shadowTable = newShadowTable - + return ShadowTableChanges(deletes: deletes, inserts: inserts, moves: moves, rowChanges: changes) } @@ -1656,7 +1578,7 @@ private extension SceneCoordinator { timelineFeed = feed fetchAndReplaceArticlesAsync(animated: animated) { - self.timelineViewController?.reinitializeArticles(resetScroll: true) + self.mainTimelineViewController?.reinitializeArticles(resetScroll: true) completion?() } } @@ -1970,7 +1892,7 @@ private extension SceneCoordinator { articles = sortedArticles updateShowNamesAndIcons() updateUnreadCount() - timelineViewController?.reloadArticles(animated: animated) + mainTimelineViewController?.reloadArticles(animated: animated) } } @@ -1980,8 +1902,8 @@ private extension SceneCoordinator { @objc func fetchAndMergeArticlesAsync() { fetchAndMergeArticlesAsync(animated: true) { - self.timelineViewController?.reinitializeArticles(resetScroll: false) - self.timelineViewController?.restoreSelectionIfNecessary(adjustScroll: false) + self.mainTimelineViewController?.reinitializeArticles(resetScroll: false) + self.mainTimelineViewController?.restoreSelectionIfNecessary(adjustScroll: false) } } @@ -2096,134 +2018,6 @@ private extension SceneCoordinator { } - // MARK: Three Panel Mode - - func installTimelineControllerIfNecessary(animated: Bool) { - if navControllerForTimeline().viewControllers.filter({ $0 is MainTimelineViewController }).count < 1 { - isTimelineViewControllerPending = true - timelineViewController = UIStoryboard.main.instantiateController(ofType: MainTimelineViewController.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() - if let subSplitViewController { - subSplitViewController.preferredDisplayMode = .oneBesideSecondary - subSplitViewController.viewControllers = [InteractiveNavigationController.template()] - subSplitViewController.preferredPrimaryColumnWidthFraction = 0.4285 - subSplitViewController.traitOverrides.horizontalSizeClass = .regular - } - } - - 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 = [feedViewController] - } - - 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) - - feedViewController.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 = [feedViewController] - if rootSplitViewController.viewControllers.last is UISplitViewController { - subSplitViewController = nil - _ = rootSplitViewController.viewControllers.popLast() - } - - if currentFeedIndexPath != nil { - timelineViewController = UIStoryboard.main.instantiateController(ofType: MainTimelineViewController.self) - timelineViewController!.coordinator = self - navigationController.pushViewController(timelineViewController!, animated: false) - } - - installArticleController(state: articleViewControllerState, animated: false) - } - // MARK: NSUserActivity func windowState() -> [AnyHashable: Any] { @@ -2258,7 +2052,7 @@ private extension SceneCoordinator { self.treeControllerDelegate.resetFilterExceptions() if let indexPath = self.indexPathFor(smartFeed) { self.selectFeed(indexPath: indexPath) { - self.feedViewController.focus() + self.mainFeedViewController.focus() } } }) @@ -2279,7 +2073,7 @@ private extension SceneCoordinator { if let folderNode = self.findFolderNode(folderName: folderName, beginningAt: accountNode), let indexPath = self.indexPathFor(folderNode) { self.selectFeed(indexPath: indexPath) { - self.feedViewController.focus() + self.mainFeedViewController.focus() } } })