mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Merge pull request #4174 from vincode-io/main
Fix state restoration when compiling against the macOS 14 SDK
This commit is contained in:
@@ -76,10 +76,10 @@ final class AppDefaults {
|
||||
firstRunDate = Date()
|
||||
return true
|
||||
}()
|
||||
|
||||
var windowState: [AnyHashable : Any]? {
|
||||
|
||||
var windowState: Data? {
|
||||
get {
|
||||
return UserDefaults.standard.object(forKey: Key.windowState) as? [AnyHashable : Any]
|
||||
return UserDefaults.standard.object(forKey: Key.windowState) as? Data
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: Key.windowState)
|
||||
|
||||
@@ -308,6 +308,10 @@ var appDelegate: AppDelegate!
|
||||
return false
|
||||
}
|
||||
|
||||
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ notification: Notification) {
|
||||
fireOldTimers()
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ enum DetailState: Equatable {
|
||||
@IBOutlet var containerView: DetailContainerView!
|
||||
@IBOutlet var statusBarView: DetailStatusBarView!
|
||||
|
||||
var windowState: DetailWindowState {
|
||||
return currentWebViewController.windowState
|
||||
}
|
||||
|
||||
lazy var regularWebViewController = {
|
||||
return createWebViewController()
|
||||
}()
|
||||
@@ -89,12 +93,6 @@ enum DetailState: Equatable {
|
||||
window.makeFirstResponderUnlessDescendantIsFirstResponder(currentWebViewController.webView)
|
||||
}
|
||||
|
||||
// MARK: State Restoration
|
||||
|
||||
func saveState(to state: inout [AnyHashable : Any]) {
|
||||
currentWebViewController.saveState(to: &state)
|
||||
}
|
||||
|
||||
// MARK: Find in Article
|
||||
|
||||
private var didLoadTextFinder = false
|
||||
|
||||
@@ -35,6 +35,10 @@ protocol DetailWebViewControllerDelegate: AnyObject {
|
||||
}
|
||||
}
|
||||
|
||||
var windowState: DetailWindowState {
|
||||
return DetailWindowState(isShowingExtractedArticle: isShowingExtractedArticle, windowScrollY: windowScrollY ?? 0)
|
||||
}
|
||||
|
||||
var article: Article? {
|
||||
switch state {
|
||||
case .article(let article, _):
|
||||
@@ -191,13 +195,6 @@ protocol DetailWebViewControllerDelegate: AnyObject {
|
||||
webView.scrollPageUp(sender)
|
||||
}
|
||||
|
||||
// MARK: State Restoration
|
||||
|
||||
func saveState(to state: inout [AnyHashable : Any]) {
|
||||
state[UserInfoKey.isShowingExtractedArticle] = isShowingExtractedArticle
|
||||
state[UserInfoKey.articleWindowScrollY] = windowScrollY
|
||||
}
|
||||
|
||||
// MARK: Find in Article
|
||||
|
||||
var canFindInArticle: Bool {
|
||||
|
||||
33
Mac/MainWindow/Detail/DetailWindowState.swift
Normal file
33
Mac/MainWindow/Detail/DetailWindowState.swift
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// DetailWindowState.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 12/16/23.
|
||||
// Copyright © 2023 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class DetailWindowState: NSObject, NSSecureCoding {
|
||||
|
||||
static var supportsSecureCoding = true
|
||||
|
||||
let isShowingExtractedArticle: Bool
|
||||
let windowScrollY: CGFloat
|
||||
|
||||
internal init(isShowingExtractedArticle: Bool, windowScrollY: CGFloat) {
|
||||
self.isShowingExtractedArticle = isShowingExtractedArticle
|
||||
self.windowScrollY = windowScrollY
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
isShowingExtractedArticle = coder.decodeBool(forKey: "isShowingExtractedArticle")
|
||||
windowScrollY = coder.decodeObject(of: NSNumber.self, forKey: "windowScrollY") as? CGFloat ?? 0
|
||||
}
|
||||
|
||||
func encode(with coder: NSCoder) {
|
||||
coder.encode(isShowingExtractedArticle, forKey: "isShowingExtractedArticle")
|
||||
coder.encode(windowScrollY, forKey: "windowScrollY")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -133,12 +133,14 @@ enum TimelineSourceMode {
|
||||
}
|
||||
|
||||
func saveStateToUserDefaults() {
|
||||
AppDefaults.shared.windowState = savableState()
|
||||
let data = try? NSKeyedArchiver.archivedData(withRootObject: savableState(), requiringSecureCoding: true)
|
||||
AppDefaults.shared.windowState = data
|
||||
window?.saveFrame(usingName: windowAutosaveName)
|
||||
}
|
||||
|
||||
func restoreStateFromUserDefaults() {
|
||||
if let state = AppDefaults.shared.windowState {
|
||||
if let data = AppDefaults.shared.windowState,
|
||||
let state = try? NSKeyedUnarchiver.unarchivedObject(ofClass: MainWindowState.self, from: data) {
|
||||
restoreState(from: state)
|
||||
window?.setFrameUsingName(windowAutosaveName, force: true)
|
||||
}
|
||||
@@ -630,7 +632,7 @@ extension MainWindowController: NSWindowDelegate {
|
||||
}
|
||||
|
||||
func window(_ window: NSWindow, didDecodeRestorableState coder: NSCoder) {
|
||||
guard let state = try? coder.decodeTopLevelObject(forKey: UserInfoKey.windowState) as? [AnyHashable : Any] else { return }
|
||||
guard let state = coder.decodeObject(of: MainWindowState.self, forKey: UserInfoKey.windowState) else { return }
|
||||
restoreState(from: state)
|
||||
}
|
||||
|
||||
@@ -1087,31 +1089,39 @@ private extension MainWindowController {
|
||||
|
||||
// MARK: - State Restoration
|
||||
|
||||
func savableState() -> [AnyHashable : Any] {
|
||||
var state = [AnyHashable : Any]()
|
||||
state[UserInfoKey.windowFullScreenState] = window?.styleMask.contains(.fullScreen) ?? false
|
||||
saveSplitViewState(to: &state)
|
||||
sidebarViewController?.saveState(to: &state)
|
||||
timelineContainerViewController?.saveState(to: &state)
|
||||
detailViewController?.saveState(to: &state)
|
||||
return state
|
||||
func savableState() -> MainWindowState {
|
||||
let isFullScreen = window?.styleMask.contains(.fullScreen) ?? false
|
||||
|
||||
let splitViewWidths: [Int]
|
||||
if let splitView = splitViewController?.splitView {
|
||||
splitViewWidths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) }
|
||||
} else {
|
||||
splitViewWidths = []
|
||||
}
|
||||
|
||||
let isSidebarHidden = sidebarSplitViewItem?.isCollapsed ?? false
|
||||
|
||||
return MainWindowState(isFullScreen: isFullScreen,
|
||||
splitViewWidths: splitViewWidths,
|
||||
isSidebarHidden: isSidebarHidden,
|
||||
sidebarWindowState: sidebarViewController?.windowState,
|
||||
timelineWindowState: timelineContainerViewController?.windowState,
|
||||
detailWindowState: detailViewController?.windowState)
|
||||
}
|
||||
|
||||
func restoreState(from state: [AnyHashable : Any]) {
|
||||
if let fullScreen = state[UserInfoKey.windowFullScreenState] as? Bool, fullScreen {
|
||||
func restoreState(from state: MainWindowState) {
|
||||
if state.isFullScreen {
|
||||
window?.toggleFullScreen(self)
|
||||
}
|
||||
restoreSplitViewState(from: state)
|
||||
|
||||
sidebarViewController?.restoreState(from: state)
|
||||
sidebarViewController?.restoreState(from: state.sidebarWindowState)
|
||||
|
||||
let articleWindowScrollY = state[UserInfoKey.articleWindowScrollY] as? CGFloat
|
||||
restoreArticleWindowScrollY = articleWindowScrollY
|
||||
timelineContainerViewController?.restoreState(from: state)
|
||||
|
||||
let isShowingExtractedArticle = state[UserInfoKey.isShowingExtractedArticle] as? Bool ?? false
|
||||
timelineContainerViewController?.restoreState(from: state.timelineWindowState)
|
||||
restoreArticleWindowScrollY = state.detailWindowState?.windowScrollY
|
||||
|
||||
let isShowingExtractedArticle = state.detailWindowState?.isShowingExtractedArticle as? Bool ?? false
|
||||
if isShowingExtractedArticle {
|
||||
restoreArticleWindowScrollY = articleWindowScrollY
|
||||
startArticleExtractorForCurrentLink()
|
||||
}
|
||||
|
||||
@@ -1379,29 +1389,17 @@ private extension MainWindowController {
|
||||
}
|
||||
}
|
||||
|
||||
func saveSplitViewState(to state: inout [AnyHashable : Any]) {
|
||||
guard let splitView = splitViewController?.splitView else {
|
||||
return
|
||||
}
|
||||
|
||||
let widths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) }
|
||||
state[MainWindowController.mainWindowWidthsStateKey] = widths
|
||||
|
||||
state[UserInfoKey.isSidebarHidden] = sidebarSplitViewItem?.isCollapsed
|
||||
}
|
||||
|
||||
func restoreSplitViewState(from state: [AnyHashable : Any]) {
|
||||
func restoreSplitViewState(from state: MainWindowState) {
|
||||
guard let splitView = splitViewController?.splitView,
|
||||
let widths = state[MainWindowController.mainWindowWidthsStateKey] as? [Int],
|
||||
widths.count == 3,
|
||||
let window = window else {
|
||||
return
|
||||
state.splitViewWidths.count == 3,
|
||||
let window = window else {
|
||||
return
|
||||
}
|
||||
|
||||
let windowWidth = Int(floor(window.frame.width))
|
||||
let dividerThickness: Int = Int(splitView.dividerThickness)
|
||||
let sidebarWidth: Int = widths[0]
|
||||
let timelineWidth: Int = widths[1]
|
||||
let sidebarWidth: Int = state.splitViewWidths[0]
|
||||
let timelineWidth: Int = state.splitViewWidths[1]
|
||||
|
||||
// Make sure the detail view has its minimum thickness, at least.
|
||||
if windowWidth < sidebarWidth + dividerThickness + timelineWidth + dividerThickness + MainWindowController.detailViewMinimumThickness {
|
||||
@@ -1410,11 +1408,9 @@ private extension MainWindowController {
|
||||
|
||||
splitView.setPosition(CGFloat(sidebarWidth), ofDividerAt: 0)
|
||||
splitView.setPosition(CGFloat(sidebarWidth + dividerThickness + timelineWidth), ofDividerAt: 1)
|
||||
|
||||
let isSidebarHidden = state[UserInfoKey.isSidebarHidden] as? Bool ?? false
|
||||
|
||||
if !(sidebarSplitViewItem?.isCollapsed ?? false) && isSidebarHidden {
|
||||
sidebarSplitViewItem?.isCollapsed = true
|
||||
|
||||
Task { @MainActor in
|
||||
sidebarSplitViewItem?.isCollapsed = state.isSidebarHidden
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
50
Mac/MainWindow/MainWindowState.swift
Normal file
50
Mac/MainWindow/MainWindowState.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// MainWindowState.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 12/16/23.
|
||||
// Copyright © 2023 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class MainWindowState: NSObject, NSSecureCoding {
|
||||
|
||||
static var supportsSecureCoding = true
|
||||
|
||||
let isFullScreen: Bool
|
||||
let splitViewWidths: [Int]
|
||||
let isSidebarHidden: Bool
|
||||
let sidebarWindowState: SidebarWindowState?
|
||||
let timelineWindowState: TimelineWindowState?
|
||||
let detailWindowState: DetailWindowState?
|
||||
|
||||
init(isFullScreen: Bool, splitViewWidths: [Int], isSidebarHidden: Bool, sidebarWindowState: SidebarWindowState? = nil, timelineWindowState: TimelineWindowState? = nil, detailWindowState: DetailWindowState? = nil) {
|
||||
self.isFullScreen = isFullScreen
|
||||
self.splitViewWidths = splitViewWidths
|
||||
self.isSidebarHidden = isSidebarHidden
|
||||
self.sidebarWindowState = sidebarWindowState
|
||||
self.timelineWindowState = timelineWindowState
|
||||
self.detailWindowState = detailWindowState
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
isFullScreen = coder.decodeBool(forKey: "isFullScreen")
|
||||
splitViewWidths = coder.decodeObject(of: [NSArray.self, NSNumber.self], forKey: "splitViewWidths") as? [Int] ?? []
|
||||
isSidebarHidden = coder.decodeBool(forKey: "isSidebarHidden")
|
||||
sidebarWindowState = coder.decodeObject(of: SidebarWindowState.self, forKey: "sidebarWindowState")
|
||||
timelineWindowState = coder.decodeObject(of: TimelineWindowState.self, forKey: "timelineWindowState")
|
||||
detailWindowState = coder.decodeObject(of: DetailWindowState.self, forKey: "detailWindowState")
|
||||
}
|
||||
|
||||
|
||||
func encode(with coder: NSCoder) {
|
||||
coder.encode(isFullScreen, forKey: "isFullScreen")
|
||||
coder.encode(splitViewWidths, forKey: "splitViewWidths")
|
||||
coder.encode(isSidebarHidden, forKey: "isSidebarHidden")
|
||||
coder.encode(sidebarWindowState, forKey: "sidebarWindowState")
|
||||
coder.encode(timelineWindowState, forKey: "timelineWindowState")
|
||||
coder.encode(detailWindowState, forKey: "detailWindowState")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,12 @@ protocol SidebarDelegate: AnyObject {
|
||||
|
||||
weak var splitViewItem: NSSplitViewItem?
|
||||
|
||||
var windowState: SidebarWindowState {
|
||||
let expandedContainers = expandedTable.compactMap { $0.userInfo as? [String: String] }
|
||||
let selectedFeeds = selectedFeeds.compactMap { $0.itemID?.userInfo as? [String: String] }
|
||||
return SidebarWindowState(isReadFiltered: isReadFiltered, expandedContainers: expandedContainers, selectedFeeds: selectedFeeds)
|
||||
}
|
||||
|
||||
private let rebuildTreeAndRestoreSelectionQueue = CoalescingQueue(name: "Rebuild Tree Queue", interval: 1.0)
|
||||
let treeControllerDelegate = FeedTreeControllerDelegate()
|
||||
lazy var treeController: TreeController = {
|
||||
@@ -97,27 +103,14 @@ protocol SidebarDelegate: AnyObject {
|
||||
|
||||
// MARK: State Restoration
|
||||
|
||||
func saveState(to state: inout [AnyHashable : Any]) {
|
||||
state[UserInfoKey.readFeedsFilterState] = isReadFiltered
|
||||
state[UserInfoKey.containerExpandedWindowState] = expandedTable.map { $0.userInfo }
|
||||
state[UserInfoKey.selectedFeedsState] = selectedFeeds.compactMap { $0.itemID?.userInfo }
|
||||
}
|
||||
|
||||
func restoreState(from state: [AnyHashable : Any]) {
|
||||
func restoreState(from state: SidebarWindowState?) {
|
||||
guard let state else { return }
|
||||
|
||||
if let containerExpandedWindowState = state[UserInfoKey.containerExpandedWindowState] as? [[AnyHashable: AnyHashable]] {
|
||||
let containerIdentifers = containerExpandedWindowState.compactMap( { ContainerIdentifier(userInfo: $0) })
|
||||
expandedTable = Set(containerIdentifers)
|
||||
}
|
||||
let containerIdentifers = state.expandedContainers.compactMap( { ContainerIdentifier(userInfo: $0) })
|
||||
expandedTable = Set(containerIdentifers)
|
||||
|
||||
guard let selectedFeedsState = state[UserInfoKey.selectedFeedsState] as? [[AnyHashable: AnyHashable]] else {
|
||||
return
|
||||
}
|
||||
|
||||
let selectedItemIdentifers = Set(selectedFeedsState.compactMap( { ItemIdentifier(userInfo: $0) }))
|
||||
for selectedItemIdentifier in selectedItemIdentifers {
|
||||
treeControllerDelegate.addFilterException(selectedItemIdentifier)
|
||||
}
|
||||
let selectedItemIdentifers = Set(state.selectedFeeds.compactMap( { ItemIdentifier(userInfo: $0) }))
|
||||
selectedItemIdentifers.forEach { treeControllerDelegate.addFilterException($0) }
|
||||
|
||||
rebuildTreeAndReloadDataIfNeeded()
|
||||
|
||||
@@ -135,9 +128,7 @@ protocol SidebarDelegate: AnyObject {
|
||||
outlineView.selectRowIndexes(selectIndexes, byExtendingSelection: false)
|
||||
focus()
|
||||
|
||||
if let readFeedsFilterState = state[UserInfoKey.readFeedsFilterState] as? Bool {
|
||||
isReadFiltered = readFeedsFilterState
|
||||
}
|
||||
isReadFiltered = state.isReadFiltered
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
37
Mac/MainWindow/Sidebar/SidebarWindowState.swift
Normal file
37
Mac/MainWindow/Sidebar/SidebarWindowState.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// SidebarWindowState.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 12/16/23.
|
||||
// Copyright © 2023 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class SidebarWindowState: NSObject, NSSecureCoding {
|
||||
|
||||
static var supportsSecureCoding = true
|
||||
|
||||
let isReadFiltered: Bool
|
||||
let expandedContainers: [[String: String]]
|
||||
let selectedFeeds: [[String: String]]
|
||||
|
||||
init(isReadFiltered: Bool, expandedContainers: [[String : String]], selectedFeeds: [[String : String]]) {
|
||||
self.isReadFiltered = isReadFiltered
|
||||
self.expandedContainers = expandedContainers
|
||||
self.selectedFeeds = selectedFeeds
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
isReadFiltered = coder.decodeBool(forKey: "isReadFiltered")
|
||||
expandedContainers = coder.decodeObject(of: [NSArray.self, NSDictionary.self, NSString.self], forKey: "expandedContainers") as? [[String: String]] ?? []
|
||||
selectedFeeds = coder.decodeObject(of: [NSArray.self, NSDictionary.self, NSString.self], forKey: "selectedFeeds") as? [[String: String]] ?? []
|
||||
}
|
||||
|
||||
func encode(with coder: NSCoder) {
|
||||
coder.encode(isReadFiltered, forKey: "isReadFiltered")
|
||||
coder.encode(expandedContainers, forKey: "expandedContainers")
|
||||
coder.encode(selectedFeeds, forKey: "selectedFeeds")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,6 +37,10 @@ protocol TimelineContainerViewControllerDelegate: AnyObject {
|
||||
view?.window?.recalculateKeyViewLoop()
|
||||
}
|
||||
}
|
||||
|
||||
var windowState: TimelineWindowState? {
|
||||
return currentTimelineViewController?.windowState
|
||||
}
|
||||
|
||||
weak var delegate: TimelineContainerViewControllerDelegate?
|
||||
|
||||
@@ -125,11 +129,9 @@ protocol TimelineContainerViewControllerDelegate: AnyObject {
|
||||
|
||||
// MARK: State Restoration
|
||||
|
||||
func saveState(to state: inout [AnyHashable : Any]) {
|
||||
regularTimelineViewController.saveState(to: &state)
|
||||
}
|
||||
|
||||
func restoreState(from state: [AnyHashable : Any]) {
|
||||
func restoreState(from state: TimelineWindowState?) {
|
||||
guard let state else { return }
|
||||
|
||||
regularTimelineViewController.restoreState(from: state)
|
||||
updateReadFilterButton()
|
||||
}
|
||||
|
||||
@@ -79,6 +79,24 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
}
|
||||
|
||||
var windowState: TimelineWindowState {
|
||||
let readArticlesFilterStateKeys = readFilterEnabledTable.keys.compactMap { $0.userInfo as? [String: String] }
|
||||
let readArticlesFilterStateValues = readFilterEnabledTable.values.compactMap( { $0 })
|
||||
|
||||
if selectedArticles.count == 1 {
|
||||
let path = selectedArticles.first!.pathUserInfo
|
||||
return TimelineWindowState(readArticlesFilterStateKeys: readArticlesFilterStateKeys,
|
||||
readArticlesFilterStateValues: readArticlesFilterStateValues,
|
||||
selectedAccountID: path[ArticlePathKey.accountID] as? String,
|
||||
selectedArticleID: path[ArticlePathKey.articleID] as? String)
|
||||
} else {
|
||||
return TimelineWindowState(readArticlesFilterStateKeys: readArticlesFilterStateKeys,
|
||||
readArticlesFilterStateValues: readArticlesFilterStateValues,
|
||||
selectedAccountID: nil,
|
||||
selectedArticleID: nil)
|
||||
}
|
||||
|
||||
}
|
||||
weak var delegate: TimelineDelegate?
|
||||
var sharingServiceDelegate: NSSharingServiceDelegate?
|
||||
|
||||
@@ -289,36 +307,21 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
|
||||
// MARK: State Restoration
|
||||
|
||||
func saveState(to state: inout [AnyHashable : Any]) {
|
||||
state[UserInfoKey.readArticlesFilterStateKeys] = readFilterEnabledTable.keys.compactMap { $0.userInfo }
|
||||
state[UserInfoKey.readArticlesFilterStateValues] = readFilterEnabledTable.values.compactMap( { $0 })
|
||||
|
||||
if selectedArticles.count == 1 {
|
||||
state[UserInfoKey.articlePath] = selectedArticles.first!.pathUserInfo
|
||||
}
|
||||
}
|
||||
|
||||
func restoreState(from state: [AnyHashable : Any]) {
|
||||
guard let readArticlesFilterStateKeys = state[UserInfoKey.readArticlesFilterStateKeys] as? [[AnyHashable: AnyHashable]],
|
||||
let readArticlesFilterStateValues = state[UserInfoKey.readArticlesFilterStateValues] as? [Bool] else {
|
||||
return
|
||||
}
|
||||
|
||||
for i in 0..<readArticlesFilterStateKeys.count {
|
||||
if let itemIdentifier = ItemIdentifier(userInfo: readArticlesFilterStateKeys[i]) {
|
||||
readFilterEnabledTable[itemIdentifier] = readArticlesFilterStateValues[i]
|
||||
func restoreState(from state: TimelineWindowState) {
|
||||
for i in 0..<state.readArticlesFilterStateKeys.count {
|
||||
if let itemIdentifier = ItemIdentifier(userInfo: state.readArticlesFilterStateKeys[i]) {
|
||||
readFilterEnabledTable[itemIdentifier] = state.readArticlesFilterStateValues[i]
|
||||
}
|
||||
}
|
||||
|
||||
if let articlePathUserInfo = state[UserInfoKey.articlePath] as? [AnyHashable : Any],
|
||||
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
|
||||
let account = AccountManager.shared.existingAccount(with: accountID),
|
||||
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String {
|
||||
if let selectedAccountID = state.selectedAccountID,
|
||||
let account = AccountManager.shared.existingAccount(with: selectedAccountID),
|
||||
let selectedArticleID = state.selectedArticleID {
|
||||
|
||||
exceptionArticleFetcher = SingleArticleFetcher(account: account, articleID: articleID)
|
||||
exceptionArticleFetcher = SingleArticleFetcher(account: account, articleID: selectedArticleID)
|
||||
fetchAndReplaceArticlesSync()
|
||||
|
||||
if let selectedIndex = articles.firstIndex(where: { $0.articleID == articleID }) {
|
||||
if let selectedIndex = articles.firstIndex(where: { $0.articleID == selectedArticleID }) {
|
||||
tableView.selectRow(selectedIndex)
|
||||
Task { @MainActor in
|
||||
self.tableView.scrollTo(row: selectedIndex)
|
||||
|
||||
41
Mac/MainWindow/Timeline/TimelineWindowState.swift
Normal file
41
Mac/MainWindow/Timeline/TimelineWindowState.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// TimelineWindowState.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 12/16/23.
|
||||
// Copyright © 2023 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class TimelineWindowState: NSObject, NSSecureCoding {
|
||||
|
||||
static var supportsSecureCoding = true
|
||||
|
||||
let readArticlesFilterStateKeys: [[String: String]]
|
||||
let readArticlesFilterStateValues: [Bool]
|
||||
let selectedAccountID: String?
|
||||
let selectedArticleID: String?
|
||||
|
||||
init(readArticlesFilterStateKeys: [[String : String]], readArticlesFilterStateValues: [Bool], selectedAccountID: String? = nil, selectedArticleID: String? = nil) {
|
||||
self.readArticlesFilterStateKeys = readArticlesFilterStateKeys
|
||||
self.readArticlesFilterStateValues = readArticlesFilterStateValues
|
||||
self.selectedAccountID = selectedAccountID
|
||||
self.selectedArticleID = selectedArticleID
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
readArticlesFilterStateKeys = coder.decodeObject(of: [NSArray.self, NSDictionary.self, NSString.self], forKey: "readArticlesFilterStateKeys") as? [[String: String]] ?? []
|
||||
readArticlesFilterStateValues = coder.decodeObject(of: [NSArray.self, NSNumber.self], forKey: "readArticlesFilterStateValues") as? [Bool] ?? []
|
||||
selectedAccountID = coder.decodeObject(of: NSString.self, forKey: "selectedAccountID") as? String
|
||||
selectedArticleID = coder.decodeObject(of: NSString.self, forKey: "selectedArticleID") as? String
|
||||
}
|
||||
|
||||
func encode(with coder: NSCoder) {
|
||||
coder.encode(readArticlesFilterStateKeys, forKey: "readArticlesFilterStateKeys")
|
||||
coder.encode(readArticlesFilterStateValues, forKey: "readArticlesFilterStateValues")
|
||||
coder.encode(selectedAccountID, forKey: "selectedAccountID")
|
||||
coder.encode(selectedArticleID, forKey: "selectedArticleID")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -92,6 +92,14 @@
|
||||
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
|
||||
5117715524E1EA0F00A2A836 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117715424E1EA0F00A2A836 /* ArticleExtractorButton.swift */; };
|
||||
5117715624E1EA0F00A2A836 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117715424E1EA0F00A2A836 /* ArticleExtractorButton.swift */; };
|
||||
5117C1F72B2DF37800A30EAB /* DetailWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117C1F62B2DF37800A30EAB /* DetailWindowState.swift */; };
|
||||
5117C1F82B2DF37800A30EAB /* DetailWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117C1F62B2DF37800A30EAB /* DetailWindowState.swift */; };
|
||||
5117C1FA2B2DF3B100A30EAB /* TimelineWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117C1F92B2DF3B100A30EAB /* TimelineWindowState.swift */; };
|
||||
5117C1FB2B2DF3B100A30EAB /* TimelineWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117C1F92B2DF3B100A30EAB /* TimelineWindowState.swift */; };
|
||||
5117C1FD2B2DF3E900A30EAB /* SidebarWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117C1FC2B2DF3E900A30EAB /* SidebarWindowState.swift */; };
|
||||
5117C1FE2B2DF3E900A30EAB /* SidebarWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117C1FC2B2DF3E900A30EAB /* SidebarWindowState.swift */; };
|
||||
5117C2002B2DF41B00A30EAB /* MainWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117C1FF2B2DF41B00A30EAB /* MainWindowState.swift */; };
|
||||
5117C2012B2DF41B00A30EAB /* MainWindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117C1FF2B2DF41B00A30EAB /* MainWindowState.swift */; };
|
||||
511B148924E5DBDD00C919BD /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 511B148824E5DBDD00C919BD /* Account */; };
|
||||
511B149824E5DC2300C919BD /* ShareDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C8BC23F3780900032075 /* ShareDefaultContainer.swift */; };
|
||||
511B149924E5DC3D00C919BD /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */; };
|
||||
@@ -1161,6 +1169,10 @@
|
||||
51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = "<group>"; };
|
||||
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = "<group>"; };
|
||||
5117715424E1EA0F00A2A836 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = "<group>"; };
|
||||
5117C1F62B2DF37800A30EAB /* DetailWindowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWindowState.swift; sourceTree = "<group>"; };
|
||||
5117C1F92B2DF3B100A30EAB /* TimelineWindowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineWindowState.swift; sourceTree = "<group>"; };
|
||||
5117C1FC2B2DF3E900A30EAB /* SidebarWindowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWindowState.swift; sourceTree = "<group>"; };
|
||||
5117C1FF2B2DF41B00A30EAB /* MainWindowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindowState.swift; sourceTree = "<group>"; };
|
||||
511B9805237DCAC90028BCAA /* UserInfoKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoKey.swift; sourceTree = "<group>"; };
|
||||
511D43EE231FBDE800FB1562 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreenPad.storyboard; sourceTree = "<group>"; };
|
||||
511D4410231FC02D00FB1562 /* KeyboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = "<group>"; };
|
||||
@@ -2263,6 +2275,7 @@
|
||||
DFC14F0928EA51AB00F6EE86 /* About */,
|
||||
8483630C2262A3FE00DA1D35 /* MainWindow.storyboard */,
|
||||
51927A0328E28D1C000AE856 /* MainWindow.swift */,
|
||||
5117C1FF2B2DF41B00A30EAB /* MainWindowState.swift */,
|
||||
519279FD28E24CCA000AE856 /* MainWindowController.swift */,
|
||||
519B8D322143397200FA689C /* SharingServiceDelegate.swift */,
|
||||
849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */,
|
||||
@@ -2421,6 +2434,7 @@
|
||||
84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */,
|
||||
849A97601ED9EB96007D329B /* SidebarOutlineView.swift */,
|
||||
849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */,
|
||||
5117C1FC2B2DF3E900A30EAB /* SidebarWindowState.swift */,
|
||||
849A97621ED9EB96007D329B /* SidebarViewController.swift */,
|
||||
84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */,
|
||||
849A97631ED9EB96007D329B /* UnreadCountView.swift */,
|
||||
@@ -2437,6 +2451,7 @@
|
||||
8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */,
|
||||
8405DD9822153B6B008CE1BF /* TimelineContainerView.swift */,
|
||||
8405DDA122168920008CE1BF /* TimelineTableView.xib */,
|
||||
5117C1F92B2DF3B100A30EAB /* TimelineWindowState.swift */,
|
||||
849A976B1ED9EBC8007D329B /* TimelineViewController.swift */,
|
||||
84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */,
|
||||
849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */,
|
||||
@@ -2465,6 +2480,7 @@
|
||||
849A977C1ED9EC42007D329B /* Detail */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5117C1F62B2DF37800A30EAB /* DetailWindowState.swift */,
|
||||
849A977E1ED9EC42007D329B /* DetailViewController.swift */,
|
||||
8405DD892213E0E3008CE1BF /* DetailContainerView.swift */,
|
||||
84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */,
|
||||
@@ -3942,6 +3958,7 @@
|
||||
65ED3FD0235DEF6C0081F399 /* Author+Scriptability.swift in Sources */,
|
||||
65ED3FD1235DEF6C0081F399 /* PseudoFeed.swift in Sources */,
|
||||
65ED3FD3235DEF6C0081F399 /* NSScriptCommand+NetNewsWire.swift in Sources */,
|
||||
5117C2012B2DF41B00A30EAB /* MainWindowState.swift in Sources */,
|
||||
B2C12C6728F4C46800373730 /* URLPasteboardWriter+NetNewsWire.swift in Sources */,
|
||||
65ED3FD4235DEF6C0081F399 /* Article+Scriptability.swift in Sources */,
|
||||
65ED3FD5235DEF6C0081F399 /* SmartFeed.swift in Sources */,
|
||||
@@ -3985,6 +4002,7 @@
|
||||
65ED3FF3235DEF6C0081F399 /* ArticleSorter.swift in Sources */,
|
||||
65ED3FF4235DEF6C0081F399 /* TimelineViewController+ContextualMenus.swift in Sources */,
|
||||
65ED3FF5235DEF6C0081F399 /* ArticleStringFormatter.swift in Sources */,
|
||||
5117C1FB2B2DF3B100A30EAB /* TimelineWindowState.swift in Sources */,
|
||||
65ED3FF6235DEF6C0081F399 /* MultilineTextFieldSizer.swift in Sources */,
|
||||
65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */,
|
||||
65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */,
|
||||
@@ -4037,6 +4055,7 @@
|
||||
6538131A2680E16C007A082C /* ExportOPMLWindowController.swift in Sources */,
|
||||
65ED4021235DEF6C0081F399 /* NNW3Document.swift in Sources */,
|
||||
65ED4022235DEF6C0081F399 /* ScriptingObject.swift in Sources */,
|
||||
5117C1FE2B2DF3E900A30EAB /* SidebarWindowState.swift in Sources */,
|
||||
65ED4023235DEF6C0081F399 /* Folder+Scriptability.swift in Sources */,
|
||||
65ED4024235DEF6C0081F399 /* TimelineCellLayout.swift in Sources */,
|
||||
51D205F028E3CF8D007C46EF /* LinkTextField.swift in Sources */,
|
||||
@@ -4046,6 +4065,7 @@
|
||||
65ED4027235DEF6C0081F399 /* UnreadIndicatorView.swift in Sources */,
|
||||
DFEB034E2A273BFE00C7573A /* UTType.swift in Sources */,
|
||||
51A9A5F22380DE520033AADF /* AddFeedDefaultContainer.swift in Sources */,
|
||||
5117C1F82B2DF37800A30EAB /* DetailWindowState.swift in Sources */,
|
||||
65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */,
|
||||
65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */,
|
||||
65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */,
|
||||
@@ -4326,6 +4346,7 @@
|
||||
84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */,
|
||||
849A975B1ED9EB0D007D329B /* ArticleUtilities.swift in Sources */,
|
||||
849ADEE8235981A0000E1B81 /* NNW3OpenPanelAccessoryViewController.swift in Sources */,
|
||||
5117C1FD2B2DF3E900A30EAB /* SidebarWindowState.swift in Sources */,
|
||||
849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */,
|
||||
84A37CB5201ECD610087C5AF /* RenameWindowController.swift in Sources */,
|
||||
84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */,
|
||||
@@ -4336,6 +4357,7 @@
|
||||
84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */,
|
||||
842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */,
|
||||
84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */,
|
||||
5117C1FA2B2DF3B100A30EAB /* TimelineWindowState.swift in Sources */,
|
||||
51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */,
|
||||
D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */,
|
||||
51BC4AFF247277E0000A6ED8 /* URL-Extensions.swift in Sources */,
|
||||
@@ -4386,6 +4408,7 @@
|
||||
8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */,
|
||||
844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */,
|
||||
848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */,
|
||||
5117C2002B2DF41B00A30EAB /* MainWindowState.swift in Sources */,
|
||||
5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */,
|
||||
849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */,
|
||||
173A64172547BE0900267F6E /* AccountType+Helpers.swift in Sources */,
|
||||
@@ -4418,6 +4441,7 @@
|
||||
DF3630EB2936183D00326FB8 /* OPMLDocument.swift in Sources */,
|
||||
5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */,
|
||||
DFEB034D2A273BFE00C7573A /* UTType.swift in Sources */,
|
||||
5117C1F72B2DF37800A30EAB /* DetailWindowState.swift in Sources */,
|
||||
178A9F9D2549449F00AB7E9D /* AddAccountsView.swift in Sources */,
|
||||
51C4CFF024D37D1F00AF9874 /* Secrets.swift in Sources */,
|
||||
849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */,
|
||||
|
||||
@@ -17,15 +17,7 @@ struct UserInfoKey {
|
||||
static let itemIdentifier = "feedIdentifier"
|
||||
|
||||
static let windowState = "windowState"
|
||||
static let windowFullScreenState = "windowFullScreenState"
|
||||
static let containerExpandedWindowState = "containerExpandedWindowState"
|
||||
static let readFeedsFilterState = "readFeedsFilterState"
|
||||
static let readArticlesFilterState = "readArticlesFilterState"
|
||||
static let readArticlesFilterStateKeys = "readArticlesFilterStateKey"
|
||||
static let readArticlesFilterStateValues = "readArticlesFilterStateValue"
|
||||
static let selectedFeedsState = "selectedFeedsState"
|
||||
static let isShowingExtractedArticle = "isShowingExtractedArticle"
|
||||
|
||||
static let articleWindowScrollY = "articleWindowScrollY"
|
||||
static let isSidebarHidden = "isSidebarHidden"
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user