Reconcile with 6.1.6-ios branch.
This commit is contained in:
parent
62ad4d6e3b
commit
ed3cf35dbf
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="AJQ-jq-uMa">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
@ -18,7 +18,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view hidden="YES" contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="h1Q-FS-jlg" customClass="ArticleSearchBar" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="777" width="414" height="36"/>
|
||||
<rect key="frame" x="0.0" y="782" width="414" height="31"/>
|
||||
<color key="backgroundColor" name="barBackgroundColor"/>
|
||||
</view>
|
||||
</subviews>
|
||||
@ -107,14 +107,14 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="FJe-Yq-33r" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2318.840579710145" y="-759.375"/>
|
||||
<point key="canvasLocation" x="451" y="-431"/>
|
||||
</scene>
|
||||
<!--Timeline-->
|
||||
<scene sceneID="fag-XH-avP">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="TimelineViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" clearsSelectionOnViewWillAppear="NO" id="Kyk-vK-QRX" customClass="MainTimelineViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController storyboardIdentifier="MainTimelineViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" clearsSelectionOnViewWillAppear="NO" id="Kyk-vK-QRX" customClass="MainTimelineViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="onDrag" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="mtv-Ik-FoJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="721"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<prototypes>
|
||||
@ -153,18 +153,32 @@
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="nzm-Gf-Xce" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1620" y="-759"/>
|
||||
<point key="canvasLocation" x="451" y="-1124"/>
|
||||
</scene>
|
||||
<!--Root Split View Controller-->
|
||||
<scene sceneID="FfI-oe-67h">
|
||||
<objects>
|
||||
<splitViewController storyboardIdentifier="RootSplitViewController" allowDoubleColumnStyle="YES" preferredDisplayMode="beside" behavior="tile" primaryBackgroundStyle="sidebar" id="AJQ-jq-uMa" customClass="RootSplitViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<connections>
|
||||
<segue destination="Kyk-vK-QRX" kind="relationship" relationship="supplementaryViewController" id="FW6-KM-3C4"/>
|
||||
<segue destination="JEX-9P-axG" kind="relationship" relationship="detailViewController" id="JbU-kn-u7r"/>
|
||||
<segue destination="7bK-jq-Zjz" kind="relationship" relationship="masterViewController" id="rFx-mT-r7a"/>
|
||||
</connections>
|
||||
</splitViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="9SW-km-PuE" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1320" y="-1123"/>
|
||||
</scene>
|
||||
<!--Feeds-->
|
||||
<scene sceneID="smW-Zh-WAh">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="FeedViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" clearsSelectionOnViewWillAppear="NO" id="7bK-jq-Zjz" customClass="FeedViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController storyboardIdentifier="MainFeedViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" clearsSelectionOnViewWillAppear="NO" id="7bK-jq-Zjz" customClass="MainFeedViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="r7i-6Z-zg0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="zNG-5C-pQm" customClass="FeedTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="zNG-5C-pQm" customClass="MainFeedTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="55.5" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="zNG-5C-pQm" id="5gB-Jr-qIo">
|
||||
@ -216,7 +230,7 @@
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Rux-fX-hf1" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="900" y="-759"/>
|
||||
<point key="canvasLocation" x="452" y="-1794"/>
|
||||
</scene>
|
||||
<!--Image View Controller-->
|
||||
<scene sceneID="TT4-oA-DBw">
|
||||
@ -417,7 +431,7 @@
|
||||
<color red="0.031372549019607843" green="0.41568627450980394" blue="0.93333333333333335" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<systemColor name="separatorColor">
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
|
@ -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
|
||||
|
0
iOS/Inspector/WebFeedInspectorViewController.swift
Normal file
0
iOS/Inspector/WebFeedInspectorViewController.swift
Normal file
@ -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 }
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// FeedViewController+Drag.swift
|
||||
// MainFeedViewController+Drag.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 11/20/19.
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// FeedViewController+Drop.swift
|
||||
// MainFeedViewController+Drop.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 11/20/19.
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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<Int>?
|
||||
var inserts: Set<Int>?
|
||||
var reloads: Set<Int>?
|
||||
var moves: Set<ShadowTableChanges.Move>?
|
||||
|
||||
|
||||
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<Int>?, inserts: Set<Int>?, reloads: Set<Int>?, moves: Set<Move>?) {
|
||||
self.section = section
|
||||
self.deletes = deletes
|
||||
@ -59,7 +59,6 @@ struct ShadowTableChanges {
|
||||
self.reloads = reloads
|
||||
self.moves = moves
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var deletes: Set<Int>?
|
||||
@ -73,5 +72,4 @@ struct ShadowTableChanges {
|
||||
self.moves = moves
|
||||
self.rowChanges = rowChanges
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
|
||||
class MainTimelineDataSource<SectionIdentifierType, ItemIdentifierType>: UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable {
|
||||
|
||||
|
||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -1,5 +1,3 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
|
||||
|
40
iOS/Resources/PrivacyInfo.xcprivacy
Normal file
40
iOS/Resources/PrivacyInfo.xcprivacy
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>3B52.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>1C8F.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeUserID</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
@ -20,3 +20,4 @@
|
||||
[[body]]
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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<ContainerIdentifier>()
|
||||
|
||||
|
||||
// Which Containers used to be expanded. Reset by rebuilding the Shadow Table.
|
||||
private var lastExpandedTable = Set<ContainerIdentifier>()
|
||||
|
||||
|
||||
// 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..<shadowTable.count {
|
||||
if let row = shadowTable[i].feedNodes.firstIndex(of: FeedNode(node)) {
|
||||
@ -703,7 +644,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
||||
exceptionArticleFetcher = SingleArticleFetcher(account: account, articleID: article.articleID)
|
||||
}
|
||||
fetchAndReplaceArticlesAsync(animated: true) {
|
||||
self.timelineViewController?.reinitializeArticles(resetScroll: resetScroll)
|
||||
self.mainTimelineViewController?.reinitializeArticles(resetScroll: resetScroll)
|
||||
}
|
||||
}
|
||||
|
||||
@ -729,7 +670,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
||||
markExpanded(containerID)
|
||||
rebuildBackingStores()
|
||||
}
|
||||
|
||||
|
||||
/// This is a special function that expects the caller to change the disclosure arrow state outside this function.
|
||||
/// Failure to do so will get the Sidebar into an invalid state.
|
||||
func expand(_ node: Node) {
|
||||
@ -737,7 +678,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
||||
lastExpandedTable.insert(containerID)
|
||||
expand(containerID)
|
||||
}
|
||||
|
||||
|
||||
func expandAllSectionsAndFolders() {
|
||||
for sectionNode in treeController.rootNode.childNodes {
|
||||
markExpanded(sectionNode)
|
||||
@ -763,7 +704,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
||||
lastExpandedTable.remove(containerID)
|
||||
collapse(containerID)
|
||||
}
|
||||
|
||||
|
||||
func collapseAllFolders() {
|
||||
for sectionNode in treeController.rootNode.childNodes {
|
||||
for topLevelNode in sectionNode.childNodes {
|
||||
@ -776,7 +717,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
||||
clearTimelineIfNoLongerAvailable()
|
||||
}
|
||||
|
||||
func feedIndexPathForCurrentTimeline() -> 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..<treeController.rootNode.numberOfChildNodes {
|
||||
|
||||
|
||||
var feedNodes = [FeedNode]()
|
||||
let sectionNode = treeController.rootNode.childAtIndex(i)!
|
||||
|
||||
|
||||
if isExpanded(sectionNode) {
|
||||
for node in sectionNode.childNodes {
|
||||
feedNodes.append(FeedNode(node))
|
||||
@ -1543,16 +1465,16 @@ private extension SceneCoordinator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let sectionID = (sectionNode.representedObject as? Account)?.accountID ?? ""
|
||||
newShadowTable.append((sectionID: sectionID, feedNodes: feedNodes))
|
||||
}
|
||||
|
||||
|
||||
// If we have a current Feed IndexPath it is no longer valid and needs reset.
|
||||
if currentFeedIndexPath != nil {
|
||||
currentFeedIndexPath = indexPathFor(timelineFeed as AnyObject)
|
||||
}
|
||||
|
||||
|
||||
// Compute the differences in the shadow table rows and the expanded table entries
|
||||
var changes = [ShadowTableChanges.RowChanges]()
|
||||
let expandedTableDifference = lastExpandedTable.symmetricDifference(expandedTable)
|
||||
@ -1561,9 +1483,9 @@ private extension SceneCoordinator {
|
||||
var moves = Set<ShadowTableChanges.Move>()
|
||||
var inserts = Set<Int>()
|
||||
var deletes = Set<Int>()
|
||||
|
||||
|
||||
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<Int>()
|
||||
|
||||
|
||||
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<ShadowTableChanges.Move>()
|
||||
var inserts = Set<Int>()
|
||||
var deletes = Set<Int>()
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user