Create shared AppDefaults and delete platform-specific AppDefaults.

This commit is contained in:
Brent Simmons 2025-01-26 21:06:22 -08:00
parent d9d47749ef
commit 2f12d9f803
46 changed files with 858 additions and 895 deletions

View File

@ -1,400 +0,0 @@
//
// AppDefaults.swift
// NetNewsWire
//
// Created by Brent Simmons on 9/22/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import AppKit
enum FontSize: Int {
case small = 0
case medium = 1
case large = 2
case veryLarge = 3
}
final class AppDefaults {
static let defaultThemeName = "Default"
static let shared = AppDefaults()
private static let smallestFontSizeRawValue = FontSize.small.rawValue
private static let largestFontSizeRawValue = FontSize.veryLarge.rawValue
let isDeveloperBuild: Bool = {
if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" {
return true
}
return false
}()
var isFirstRun: Bool = {
if UserDefaults.standard.object(forKey: AppDefaultsKey.firstRunDate) as? Date == nil {
firstRunDate = Date()
return true
}
return true
}()
var windowState: [AnyHashable: Any]? {
get {
return UserDefaults.standard.object(forKey: AppDefaultsKey.windowState) as? [AnyHashable: Any]
}
set {
UserDefaults.standard.set(newValue, forKey: AppDefaultsKey.windowState)
}
}
var lastImageCacheFlushDate: Date? {
get {
return AppDefaults.date(for: AppDefaultsKey.lastImageCacheFlushDate)
}
set {
AppDefaults.setDate(for: AppDefaultsKey.lastImageCacheFlushDate, newValue)
}
}
var openInBrowserInBackground: Bool {
get {
return AppDefaults.bool(for: AppDefaultsKey.openInBrowserInBackground)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.openInBrowserInBackground, newValue)
}
}
// Special case for this default: store/retrieve it from the shared app group
// defaults, so that it can be resolved by the Safari App Extension.
var subscribeToFeedDefaults: UserDefaults {
if let appGroupID = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String,
let appGroupDefaults = UserDefaults(suiteName: appGroupID) {
return appGroupDefaults
} else {
return UserDefaults.standard
}
}
var subscribeToFeedsInDefaultBrowser: Bool {
get {
return subscribeToFeedDefaults.bool(forKey: AppDefaultsKey.subscribeToFeedsInDefaultBrowser)
}
set {
subscribeToFeedDefaults.set(newValue, forKey: AppDefaultsKey.subscribeToFeedsInDefaultBrowser)
}
}
var sidebarFontSize: FontSize {
get {
return fontSize(for: AppDefaultsKey.sidebarFontSize)
}
set {
AppDefaults.setFontSize(for: AppDefaultsKey.sidebarFontSize, newValue)
}
}
var timelineFontSize: FontSize {
get {
return fontSize(for: AppDefaultsKey.timelineFontSize)
}
set {
AppDefaults.setFontSize(for: AppDefaultsKey.timelineFontSize, newValue)
}
}
var detailFontSize: FontSize {
get {
return fontSize(for: AppDefaultsKey.detailFontSize)
}
set {
AppDefaults.setFontSize(for: AppDefaultsKey.detailFontSize, newValue)
}
}
var addFeedAccountID: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.addFeedAccountID)
}
set {
AppDefaults.setString(for: AppDefaultsKey.addFeedAccountID, newValue)
}
}
var addFeedFolderName: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.addFeedFolderName)
}
set {
AppDefaults.setString(for: AppDefaultsKey.addFeedFolderName, newValue)
}
}
var addFolderAccountID: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.addFolderAccountID)
}
set {
AppDefaults.setString(for: AppDefaultsKey.addFolderAccountID, newValue)
}
}
var importOPMLAccountID: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.importOPMLAccountID)
}
set {
AppDefaults.setString(for: AppDefaultsKey.importOPMLAccountID, newValue)
}
}
var exportOPMLAccountID: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.exportOPMLAccountID)
}
set {
AppDefaults.setString(for: AppDefaultsKey.exportOPMLAccountID, newValue)
}
}
var defaultBrowserID: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.defaultBrowserID)
}
set {
AppDefaults.setString(for: AppDefaultsKey.defaultBrowserID, newValue)
}
}
var currentThemeName: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.currentThemeName)
}
set {
AppDefaults.setString(for: AppDefaultsKey.currentThemeName, newValue)
}
}
var showTitleOnMainWindow: Bool {
return AppDefaults.bool(for: AppDefaultsKey.showTitleOnMainWindow)
}
var showDebugMenu: Bool {
return AppDefaults.bool(for: AppDefaultsKey.showDebugMenu)
}
var feedDoubleClickMarkAsRead: Bool {
get {
return AppDefaults.bool(for: AppDefaultsKey.feedDoubleClickMarkAsRead)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.feedDoubleClickMarkAsRead, newValue)
}
}
var suppressSyncOnLaunch: Bool {
get {
return AppDefaults.bool(for: AppDefaultsKey.suppressSyncOnLaunch)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.suppressSyncOnLaunch, newValue)
}
}
var webInspectorEnabled: Bool {
get {
return AppDefaults.bool(for: AppDefaultsKey.webInspectorEnabled)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.webInspectorEnabled, newValue)
}
}
var webInspectorStartsAttached: Bool {
get {
return AppDefaults.bool(for: AppDefaultsKey.webInspectorStartsAttached)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.webInspectorStartsAttached, newValue)
}
}
var timelineSortDirection: ComparisonResult {
get {
return AppDefaults.sortDirection(for: AppDefaultsKey.timelineSortDirection)
}
set {
AppDefaults.setSortDirection(for: AppDefaultsKey.timelineSortDirection, newValue)
}
}
var timelineGroupByFeed: Bool {
get {
return AppDefaults.bool(for: AppDefaultsKey.timelineGroupByFeed)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.timelineGroupByFeed, newValue)
}
}
var timelineShowsSeparators: Bool {
return AppDefaults.bool(for: AppDefaultsKey.timelineShowsSeparators)
}
var articleTextSize: ArticleTextSize {
get {
let rawValue = UserDefaults.standard.integer(forKey: AppDefaultsKey.articleTextSize)
return ArticleTextSize(rawValue: rawValue) ?? ArticleTextSize.large
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: AppDefaultsKey.articleTextSize)
}
}
var refreshInterval: RefreshInterval {
get {
let rawValue = UserDefaults.standard.integer(forKey: AppDefaultsKey.refreshInterval)
return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: AppDefaultsKey.refreshInterval)
}
}
var isArticleContentJavascriptEnabled: Bool {
get {
UserDefaults.standard.bool(forKey: AppDefaultsKey.articleContentJavascriptEnabled)
}
set {
UserDefaults.standard.set(newValue, forKey: AppDefaultsKey.articleContentJavascriptEnabled)
}
}
func registerDefaults() {
#if DEBUG
let showDebugMenu = true
#else
let showDebugMenu = false
#endif
let defaults: [String: Any] = [
AppDefaultsKey.sidebarFontSize: FontSize.medium.rawValue,
AppDefaultsKey.timelineFontSize: FontSize.medium.rawValue,
AppDefaultsKey.detailFontSize: FontSize.medium.rawValue,
AppDefaultsKey.timelineSortDirection: ComparisonResult.orderedDescending.rawValue,
AppDefaultsKey.timelineGroupByFeed: false,
"NSScrollViewShouldScrollUnderTitlebar": false,
AppDefaultsKey.refreshInterval: RefreshInterval.everyHour.rawValue,
AppDefaultsKey.showDebugMenu: showDebugMenu,
AppDefaultsKey.currentThemeName: Self.defaultThemeName,
AppDefaultsKey.articleContentJavascriptEnabled: true
]
UserDefaults.standard.register(defaults: defaults)
// It seems that registering a default for NSQuitAlwaysKeepsWindows to true
// is not good enough to get the system to respect it, so we have to literally
// set it as the default to get it to take effect. This overrides a system-wide
// setting in the System Preferences, which is ostensibly meant to "close windows"
// in an app, but has the side-effect of also not preserving or restoring any state
// for the window. Since we've switched to using the standard state preservation and
// restoration mechanisms, and because it seems highly unlikely any user would object
// to NetNewsWire preserving this state, we'll force the preference on. If this becomes
// an issue, this could be changed to proactively look for whether the default has been
// set _by the user_ to false, and respect that default if it is so-set.
// UserDefaults.standard.set(true, forKey: "NSQuitAlwaysKeepsWindows")
// TODO: revisit the above when coming back to state restoration issues.
}
func actualFontSize(for fontSize: FontSize) -> CGFloat {
switch fontSize {
case .small:
return NSFont.systemFontSize
case .medium:
return actualFontSize(for: .small) + 1.0
case .large:
return actualFontSize(for: .medium) + 4.0
case .veryLarge:
return actualFontSize(for: .large) + 8.0
}
}
}
private extension AppDefaults {
static var firstRunDate: Date? {
get {
return AppDefaults.date(for: AppDefaultsKey.firstRunDate)
}
set {
AppDefaults.setDate(for: AppDefaultsKey.firstRunDate, newValue)
}
}
func fontSize(for key: String) -> FontSize {
// Punted till after 1.0.
return .medium
// var rawFontSize = int(for: key)
// if rawFontSize < smallestFontSizeRawValue {
// rawFontSize = smallestFontSizeRawValue
// }
// if rawFontSize > largestFontSizeRawValue {
// rawFontSize = largestFontSizeRawValue
// }
// return FontSize(rawValue: rawFontSize)!
}
static func setFontSize(for key: String, _ fontSize: FontSize) {
setInt(for: key, fontSize.rawValue)
}
static func string(for key: String) -> String? {
return UserDefaults.standard.string(forKey: key)
}
static func setString(for key: String, _ value: String?) {
UserDefaults.standard.set(value, forKey: key)
}
static func bool(for key: String) -> Bool {
return UserDefaults.standard.bool(forKey: key)
}
static func setBool(for key: String, _ flag: Bool) {
UserDefaults.standard.set(flag, forKey: key)
}
static func int(for key: String) -> Int {
return UserDefaults.standard.integer(forKey: key)
}
static func setInt(for key: String, _ x: Int) {
UserDefaults.standard.set(x, forKey: key)
}
static func date(for key: String) -> Date? {
return UserDefaults.standard.object(forKey: key) as? Date
}
static func setDate(for key: String, _ date: Date?) {
UserDefaults.standard.set(date, forKey: key)
}
static func sortDirection(for key: String) -> ComparisonResult {
let rawInt = int(for: key)
if rawInt == ComparisonResult.orderedAscending.rawValue {
return .orderedAscending
}
return .orderedDescending
}
static func setSortDirection(for key: String, _ value: ComparisonResult) {
if value == .orderedAscending {
setInt(for: key, ComparisonResult.orderedAscending.rawValue)
} else {
setInt(for: key, ComparisonResult.orderedDescending.rawValue)
}
}
}

View File

@ -37,7 +37,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
var refreshTimer: AccountRefreshTimer? var refreshTimer: AccountRefreshTimer?
var syncTimer: ArticleStatusSyncTimer? var syncTimer: ArticleStatusSyncTimer?
var lastRefreshInterval = AppDefaults.shared.refreshInterval var lastRefreshInterval = AppDefaults.refreshInterval
var shuttingDown = false { var shuttingDown = false {
didSet { didSet {
@ -168,8 +168,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
NSLog("Failed to start software updater with error: \(error)") NSLog("Failed to start software updater with error: \(error)")
} }
AppDefaults.shared.registerDefaults() AppDefaults.registerDefaults()
let isFirstRun = AppDefaults.shared.isFirstRun let isFirstRun = AppDefaults.isFirstRun
if isFirstRun { if isFirstRun {
os_log(.debug, "Is first run.") os_log(.debug, "Is first run.")
} }
@ -228,7 +228,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
refreshTimer!.update() refreshTimer!.update()
syncTimer!.update() syncTimer!.update()
#else #else
if AppDefaults.shared.suppressSyncOnLaunch { if AppDefaults.suppressSyncOnLaunch {
refreshTimer!.update() refreshTimer!.update()
syncTimer!.update() syncTimer!.update()
} else { } else {
@ -239,7 +239,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
} }
#endif #endif
if !AppDefaults.shared.showDebugMenu { if !AppDefaults.showDebugMenu {
debugMenuItem.menu?.removeItem(debugMenuItem) debugMenuItem.menu?.removeItem(debugMenuItem)
} }
@ -330,9 +330,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
updateSortMenuItems() updateSortMenuItems()
updateGroupByFeedMenuItem() updateGroupByFeedMenuItem()
if lastRefreshInterval != AppDefaults.shared.refreshInterval { if lastRefreshInterval != AppDefaults.refreshInterval {
refreshTimer?.update() refreshTimer?.update()
lastRefreshInterval = AppDefaults.shared.refreshInterval lastRefreshInterval = AppDefaults.refreshInterval
} }
updateDockBadge() updateDockBadge()
@ -426,7 +426,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
} }
if item.action == #selector(toggleWebInspectorEnabled(_:)) { if item.action == #selector(toggleWebInspectorEnabled(_:)) {
(item as! NSMenuItem).state = AppDefaults.shared.webInspectorEnabled ? .on : .off (item as! NSMenuItem).state = AppDefaults.webInspectorEnabled ? .on : .off
} }
return true return true
@ -634,16 +634,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
@IBAction func sortByOldestArticleOnTop(_ sender: Any?) { @IBAction func sortByOldestArticleOnTop(_ sender: Any?) {
AppDefaults.shared.timelineSortDirection = .orderedAscending AppDefaults.timelineSortDirection = .orderedAscending
} }
@IBAction func sortByNewestArticleOnTop(_ sender: Any?) { @IBAction func sortByNewestArticleOnTop(_ sender: Any?) {
AppDefaults.shared.timelineSortDirection = .orderedDescending AppDefaults.timelineSortDirection = .orderedDescending
} }
@IBAction func groupByFeedToggled(_ sender: NSMenuItem) { @IBAction func groupByFeedToggled(_ sender: NSMenuItem) {
AppDefaults.shared.timelineGroupByFeed.toggle() AppDefaults.timelineGroupByFeed.toggle()
} }
@IBAction func checkForUpdates(_ sender: Any?) { @IBAction func checkForUpdates(_ sender: Any?) {
@ -691,13 +691,13 @@ extension AppDelegate {
@IBAction func toggleWebInspectorEnabled(_ sender: Any?) { @IBAction func toggleWebInspectorEnabled(_ sender: Any?) {
let newValue = !AppDefaults.shared.webInspectorEnabled let newValue = !AppDefaults.webInspectorEnabled
AppDefaults.shared.webInspectorEnabled = newValue AppDefaults.webInspectorEnabled = newValue
// An attached inspector can display incorrectly on certain setups (like mine); default to displaying in a separate window, // An attached inspector can display incorrectly on certain setups (like mine); default to displaying in a separate window,
// and reset the default to a separate window when the preference is toggled off and on again in case the inspector is // and reset the default to a separate window when the preference is toggled off and on again in case the inspector is
// accidentally reattached. // accidentally reattached.
AppDefaults.shared.webInspectorStartsAttached = false AppDefaults.webInspectorStartsAttached = false
NotificationCenter.default.post(name: .WebInspectorEnabledDidChange, object: newValue) NotificationCenter.default.post(name: .WebInspectorEnabledDidChange, object: newValue)
} }
} }
@ -724,13 +724,13 @@ internal extension AppDelegate {
} }
func updateSortMenuItems() { func updateSortMenuItems() {
let sortByNewestOnTop = AppDefaults.shared.timelineSortDirection == .orderedDescending let sortByNewestOnTop = AppDefaults.timelineSortDirection == .orderedDescending
sortByNewestArticleOnTopMenuItem.state = sortByNewestOnTop ? .on : .off sortByNewestArticleOnTopMenuItem.state = sortByNewestOnTop ? .on : .off
sortByOldestArticleOnTopMenuItem.state = sortByNewestOnTop ? .off : .on sortByOldestArticleOnTopMenuItem.state = sortByNewestOnTop ? .off : .on
} }
func updateGroupByFeedMenuItem() { func updateGroupByFeedMenuItem() {
let groupByFeedEnabled = AppDefaults.shared.timelineGroupByFeed let groupByFeedEnabled = AppDefaults.timelineGroupByFeed
groupArticlesByFeedMenuItem.state = groupByFeedEnabled ? .on : .off groupArticlesByFeedMenuItem.state = groupByFeedEnabled ? .on : .off
} }

View File

@ -16,7 +16,7 @@ struct Browser {
/// The user-assigned default browser, or `nil` if none was assigned /// The user-assigned default browser, or `nil` if none was assigned
/// (i.e., the system default should be used). /// (i.e., the system default should be used).
static var defaultBrowser: MacWebBrowser? { static var defaultBrowser: MacWebBrowser? {
if let bundleID = AppDefaults.shared.defaultBrowserID, let browser = MacWebBrowser(bundleIdentifier: bundleID) { if let bundleID = AppDefaults.defaultBrowserID, let browser = MacWebBrowser(bundleIdentifier: bundleID) {
return browser return browser
} }
@ -30,7 +30,7 @@ struct Browser {
/// - invert: Whether to invert the "open in background in browser" preference /// - invert: Whether to invert the "open in background in browser" preference
static func open(_ urlString: String, invertPreference invert: Bool = false) { static func open(_ urlString: String, invertPreference invert: Bool = false) {
// Opens according to prefs. // Opens according to prefs.
open(urlString, inBackground: invert ? !AppDefaults.shared.openInBrowserInBackground : AppDefaults.shared.openInBrowserInBackground) open(urlString, inBackground: invert ? !AppDefaults.openInBrowserInBackground : AppDefaults.openInBrowserInBackground)
} }
/// Opens a URL in the default browser. /// Opens a URL in the default browser.
@ -64,7 +64,7 @@ struct Browser {
extension Browser { extension Browser {
static var titleForOpenInBrowserInverted: String { static var titleForOpenInBrowserInverted: String {
let openInBackgroundPref = AppDefaults.shared.openInBrowserInBackground let openInBackgroundPref = AppDefaults.openInBrowserInBackground
return openInBackgroundPref ? return openInBackgroundPref ?
NSLocalizedString("Open in Browser in Foreground", comment: "Open in Browser in Foreground menu item title") : NSLocalizedString("Open in Browser in Foreground", comment: "Open in Browser in Foreground menu item title") :

View File

@ -36,7 +36,7 @@ final class AddFolderWindowController: NSWindowController {
// MARK: - NSViewController // MARK: - NSViewController
override func windowDidLoad() { override func windowDidLoad() {
let preferredAccountID = AppDefaults.shared.addFolderAccountID let preferredAccountID = AppDefaults.addFolderAccountID
accountPopupButton.removeAllItems() accountPopupButton.removeAllItems()
let menu = NSMenu() let menu = NSMenu()
@ -93,7 +93,7 @@ private extension AddFolderWindowController {
} }
let account = menuItem.representedObject as! Account let account = menuItem.representedObject as! Account
AppDefaults.shared.addFolderAccountID = account.accountID AppDefaults.addFolderAccountID = account.accountID
let folderName = self.folderNameTextField.stringValue let folderName = self.folderNameTextField.stringValue
if folderName.isEmpty { if folderName.isEmpty {

View File

@ -57,7 +57,7 @@ final class DetailViewController: NSViewController, WKUIDelegate {
} }
} }
private var isArticleContentJavascriptEnabled = AppDefaults.shared.isArticleContentJavascriptEnabled private var isArticleContentJavascriptEnabled = AppDefaults.isArticleContentJavascriptEnabled
override func viewDidLoad() { override func viewDidLoad() {
currentWebViewController = regularWebViewController currentWebViewController = regularWebViewController
@ -159,8 +159,8 @@ private extension DetailViewController {
} }
@objc func userDefaultsDidChange(_: Notification) { @objc func userDefaultsDidChange(_: Notification) {
if AppDefaults.shared.isArticleContentJavascriptEnabled != isArticleContentJavascriptEnabled { if AppDefaults.isArticleContentJavascriptEnabled != isArticleContentJavascriptEnabled {
isArticleContentJavascriptEnabled = AppDefaults.shared.isArticleContentJavascriptEnabled isArticleContentJavascriptEnabled = AppDefaults.isArticleContentJavascriptEnabled
createNewWebViewsAndRestoreState() createNewWebViewsAndRestoreState()
} }
} }

View File

@ -46,7 +46,7 @@ final class DetailWebViewController: NSViewController {
} }
} }
private var articleTextSize = AppDefaults.shared.articleTextSize private var articleTextSize = AppDefaults.articleTextSize
private var webInspectorEnabled: Bool { private var webInspectorEnabled: Bool {
get { get {
@ -97,7 +97,7 @@ final class DetailWebViewController: NSViewController {
webView.isHidden = true webView.isHidden = true
waitingForFirstReload = true waitingForFirstReload = true
webInspectorEnabled = AppDefaults.shared.webInspectorEnabled webInspectorEnabled = AppDefaults.webInspectorEnabled
NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .feedIconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .feedIconDidBecomeAvailable, object: nil)
@ -124,8 +124,8 @@ final class DetailWebViewController: NSViewController {
} }
@objc func userDefaultsDidChange(_ note: Notification) { @objc func userDefaultsDidChange(_ note: Notification) {
if articleTextSize != AppDefaults.shared.articleTextSize { if articleTextSize != AppDefaults.articleTextSize {
articleTextSize = AppDefaults.shared.articleTextSize articleTextSize = AppDefaults.articleTextSize
reloadHTMLMaintainingScrollPosition() reloadHTMLMaintainingScrollPosition()
} }
} }

View File

@ -128,12 +128,12 @@ final class MainWindowController: NSWindowController, NSUserInterfaceValidations
} }
func saveStateToUserDefaults() { func saveStateToUserDefaults() {
AppDefaults.shared.windowState = savableState() AppDefaults.windowState = savableState()
window?.saveFrame(usingName: windowAutosaveName) window?.saveFrame(usingName: windowAutosaveName)
} }
func restoreStateFromUserDefaults() { func restoreStateFromUserDefaults() {
if let state = AppDefaults.shared.windowState { if let state = AppDefaults.windowState {
restoreState(from: state) restoreState(from: state)
window?.setFrameUsingName(windowAutosaveName, force: true) window?.setFrameUsingName(windowAutosaveName, force: true)
} }
@ -314,7 +314,7 @@ final class MainWindowController: NSWindowController, NSUserInterfaceValidations
} }
@IBAction func openInBrowser(_ sender: Any?) { @IBAction func openInBrowser(_ sender: Any?) {
if AppDefaults.shared.openInBrowserInBackground { if AppDefaults.openInBrowserInBackground {
window?.makeKeyAndOrderFront(self) window?.makeKeyAndOrderFront(self)
} }
openArticleInBrowser(sender) openArticleInBrowser(sender)
@ -326,11 +326,11 @@ final class MainWindowController: NSWindowController, NSUserInterfaceValidations
} }
@IBAction func openInBrowserUsingOppositeOfSettings(_ sender: Any?) { @IBAction func openInBrowserUsingOppositeOfSettings(_ sender: Any?) {
if !AppDefaults.shared.openInBrowserInBackground { if !AppDefaults.openInBrowserInBackground {
window?.makeKeyAndOrderFront(self) window?.makeKeyAndOrderFront(self)
} }
if let link = currentLink { if let link = currentLink {
Browser.open(link, inBackground: !AppDefaults.shared.openInBrowserInBackground) Browser.open(link, inBackground: !AppDefaults.openInBrowserInBackground)
} }
} }
@ -1040,7 +1040,7 @@ private extension MainWindowController {
} }
func validateToggleArticleExtractor(_ item: NSValidatedUserInterfaceItem) -> Bool { func validateToggleArticleExtractor(_ item: NSValidatedUserInterfaceItem) -> Bool {
guard !AppDefaults.shared.isDeveloperBuild else { guard !AppDefaults.isDeveloperBuild else {
return false return false
} }

View File

@ -82,7 +82,7 @@ private extension NNW3ImportController {
guard let account = accessoryViewController.selectedAccount else { guard let account = accessoryViewController.selectedAccount else {
return return
} }
AppDefaults.shared.importOPMLAccountID = account.accountID AppDefaults.importOPMLAccountID = account.accountID
NNW3ImportController.importSubscriptionsPlist(subscriptionsPlistURL, into: account) NNW3ImportController.importSubscriptionsPlist(subscriptionsPlistURL, into: account)
} }

View File

@ -39,7 +39,7 @@ final class NNW3OpenPanelAccessoryViewController: NSViewController {
menuItem.representedObject = account menuItem.representedObject = account
menu.addItem(menuItem) menu.addItem(menuItem)
if account.accountID == AppDefaults.shared.importOPMLAccountID { if account.accountID == AppDefaults.importOPMLAccountID {
accountPopUpButton.select(menuItem) accountPopUpButton.select(menuItem)
} }
} }

View File

@ -32,7 +32,7 @@ final class ExportOPMLWindowController: NSWindowController {
oneMenuItem.representedObject = oneAccount oneMenuItem.representedObject = oneAccount
menu.addItem(oneMenuItem) menu.addItem(oneMenuItem)
if oneAccount.accountID == AppDefaults.shared.exportOPMLAccountID { if oneAccount.accountID == AppDefaults.exportOPMLAccountID {
accountPopUpButton.select(oneMenuItem) accountPopUpButton.select(oneMenuItem)
} }
@ -67,7 +67,7 @@ final class ExportOPMLWindowController: NSWindowController {
} }
let account = menuItem.representedObject as! Account let account = menuItem.representedObject as! Account
AppDefaults.shared.exportOPMLAccountID = account.accountID AppDefaults.exportOPMLAccountID = account.accountID
hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK)
exportOPML(account: account) exportOPML(account: account)

View File

@ -36,7 +36,7 @@ final class ImportOPMLWindowController: NSWindowController {
oneMenuItem.representedObject = oneAccount oneMenuItem.representedObject = oneAccount
menu.addItem(oneMenuItem) menu.addItem(oneMenuItem)
if oneAccount.accountID == AppDefaults.shared.importOPMLAccountID { if oneAccount.accountID == AppDefaults.importOPMLAccountID {
accountPopUpButton.select(oneMenuItem) accountPopUpButton.select(oneMenuItem)
} }
@ -71,7 +71,7 @@ final class ImportOPMLWindowController: NSWindowController {
} }
let account = menuItem.representedObject as! Account let account = menuItem.representedObject as! Account
AppDefaults.shared.importOPMLAccountID = account.accountID AppDefaults.importOPMLAccountID = account.accountID
hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK)
importOPML(account: account) importOPML(account: account)

View File

@ -257,7 +257,7 @@ protocol SidebarDelegate: AnyObject {
guard outlineView.clickedRow == outlineView.selectedRow else { guard outlineView.clickedRow == outlineView.selectedRow else {
return return
} }
if AppDefaults.shared.feedDoubleClickMarkAsRead, let articles = try? singleSelectedFeed?.fetchUnreadArticles() { if AppDefaults.feedDoubleClickMarkAsRead, let articles = try? singleSelectedFeed?.fetchUnreadArticles() {
if let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) { if let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) {
runCommand(markReadCommand) runCommand(markReadCommand)
} }

View File

@ -44,7 +44,7 @@ struct TimelineCellAppearance: Equatable {
init(showIcon: Bool, fontSize: FontSize) { init(showIcon: Bool, fontSize: FontSize) {
let actualFontSize = AppDefaults.shared.actualFontSize(for: fontSize) let actualFontSize = AppDefaults.actualFontSize(for: fontSize)
let smallItemFontSize = floor(actualFontSize * 0.90) let smallItemFontSize = floor(actualFontSize * 0.90)
let largeItemFontSize = actualFontSize let largeItemFontSize = actualFontSize

View File

@ -178,7 +178,7 @@ private extension TimelineContainerViewController {
} }
func updateViewOptionsPopUpButton() { func updateViewOptionsPopUpButton() {
if AppDefaults.shared.timelineSortDirection == .orderedAscending { if AppDefaults.timelineSortDirection == .orderedAscending {
newestToOldestMenuItem.state = .off newestToOldestMenuItem.state = .off
oldestToNewestMenuItem.state = .on oldestToNewestMenuItem.state = .on
viewOptionsPopUpButton.setTitle(oldestToNewestMenuItem.title) viewOptionsPopUpButton.setTitle(oldestToNewestMenuItem.title)
@ -188,7 +188,7 @@ private extension TimelineContainerViewController {
viewOptionsPopUpButton.setTitle(newestToOldestMenuItem.title) viewOptionsPopUpButton.setTitle(newestToOldestMenuItem.title)
} }
if AppDefaults.shared.timelineGroupByFeed == true { if AppDefaults.timelineGroupByFeed == true {
groupByFeedMenuItem.state = .on groupByFeedMenuItem.state = .on
} else { } else {
groupByFeedMenuItem.state = .off groupByFeedMenuItem.state = .off

View File

@ -47,7 +47,7 @@ final class TimelineTableRowView: NSTableRowView {
} }
override func viewDidMoveToSuperview() { override func viewDidMoveToSuperview() {
if AppDefaults.shared.timelineShowsSeparators { if AppDefaults.timelineShowsSeparators {
addSeparatorView() addSeparatorView()
} }
} }

View File

@ -161,21 +161,21 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
private var didRegisterForNotifications = false private var didRegisterForNotifications = false
static let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5, maxInterval: 2.0) static let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5, maxInterval: 2.0)
private var sortDirection = AppDefaults.shared.timelineSortDirection { private var sortDirection = AppDefaults.timelineSortDirection {
didSet { didSet {
if sortDirection != oldValue { if sortDirection != oldValue {
sortParametersDidChange() sortParametersDidChange()
} }
} }
} }
private var groupByFeed = AppDefaults.shared.timelineGroupByFeed { private var groupByFeed = AppDefaults.timelineGroupByFeed {
didSet { didSet {
if groupByFeed != oldValue { if groupByFeed != oldValue {
sortParametersDidChange() sortParametersDidChange()
} }
} }
} }
private var fontSize: FontSize = AppDefaults.shared.timelineFontSize { private var fontSize: FontSize = AppDefaults.timelineFontSize {
didSet { didSet {
if fontSize != oldValue { if fontSize != oldValue {
fontSizeDidChange() fontSizeDidChange()
@ -657,9 +657,9 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
} }
@objc func userDefaultsDidChange(_ note: Notification) { @objc func userDefaultsDidChange(_ note: Notification) {
self.fontSize = AppDefaults.shared.timelineFontSize self.fontSize = AppDefaults.timelineFontSize
self.sortDirection = AppDefaults.shared.timelineSortDirection self.sortDirection = AppDefaults.timelineSortDirection
self.groupByFeed = AppDefaults.shared.timelineGroupByFeed self.groupByFeed = AppDefaults.timelineGroupByFeed
} }
// MARK: - Reloading Data // MARK: - Reloading Data

View File

@ -20,7 +20,7 @@ struct AddAccountHelpView: View {
VStack { VStack {
HStack { HStack {
ForEach(accountTypes, id: \.self) { accountType in ForEach(accountTypes, id: \.self) { accountType in
if !(AppDefaults.shared.isDeveloperBuild && accountType.isDeveloperRestricted) { if !(AppDefaults.isDeveloperBuild && accountType.isDeveloperRestricted) {
Button(action: { Button(action: {
if accountType == .cloudKit && AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit }) { if accountType == .cloudKit && AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit }) {
iCloudUnavailableError = true iCloudUnavailableError = true

View File

@ -54,7 +54,7 @@ enum AddAccountSections: Int, CaseIterable {
case .icloud: case .icloud:
return [.cloudKit] return [.cloudKit]
case .web: case .web:
if AppDefaults.shared.isDeveloperBuild { if AppDefaults.isDeveloperBuild {
return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader].filter({ $0.isDeveloperRestricted == false }) return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader].filter({ $0.isDeveloperRestricted == false })
} else { } else {
return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader] return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader]
@ -90,7 +90,7 @@ struct AddAccountsView: View {
localAccount localAccount
if !AppDefaults.shared.isDeveloperBuild { if !AppDefaults.isDeveloperBuild {
icloudAccount icloudAccount
} }

View File

@ -65,7 +65,7 @@ final class GeneralPreferencesViewController: NSViewController {
return return
} }
let bundleID = menuItem.representedObject as? String let bundleID = menuItem.representedObject as? String
AppDefaults.shared.defaultBrowserID = bundleID AppDefaults.defaultBrowserID = bundleID
updateBrowserPopup() updateBrowserPopup()
} }
@ -132,7 +132,7 @@ private extension GeneralPreferencesViewController {
menu.addItem(item) menu.addItem(item)
} }
defaultBrowserPopup.selectItem(at: defaultBrowserPopup.indexOfItem(withRepresentedObject: AppDefaults.shared.defaultBrowserID)) defaultBrowserPopup.selectItem(at: defaultBrowserPopup.indexOfItem(withRepresentedObject: AppDefaults.defaultBrowserID))
} }
func updateNotificationSettings() { func updateNotificationSettings() {
@ -161,10 +161,10 @@ private extension GeneralPreferencesViewController {
@objc var openFeedsInDefaultNewsReader: Bool { @objc var openFeedsInDefaultNewsReader: Bool {
get { get {
return AppDefaults.shared.subscribeToFeedsInDefaultBrowser return AppDefaults.subscribeToFeedsInDefaultBrowser
} }
set { set {
AppDefaults.shared.subscribeToFeedsInDefaultBrowser = newValue AppDefaults.subscribeToFeedsInDefaultBrowser = newValue
} }
} }
} }

View File

@ -277,6 +277,17 @@
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
848D19082D46D78E00DD0819 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Defaults/AppDefaults.swift,
Defaults/ArticleTextSize.swift,
Defaults/FontSize.swift,
Defaults/RefreshInterval.swift,
Defaults/UserInterfaceColorPalette.swift,
);
target = 6581C73220CED60000F4AD34 /* Subscribe to Feed */;
};
8498E53C2D27A766009F5438 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { 8498E53C2D27A766009F5438 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet; isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = ( membershipExceptions = (
@ -298,7 +309,6 @@
membershipExceptions = ( membershipExceptions = (
/Localized/ShareExtension/MainInterface.storyboard, /Localized/ShareExtension/MainInterface.storyboard,
AppAssets.swift, AppAssets.swift,
AppDefaults.swift,
Resources/Assets.xcassets, Resources/Assets.xcassets,
ShareExtension/ShareFolderPickerAccountCell.xib, ShareExtension/ShareFolderPickerAccountCell.xib,
ShareExtension/ShareFolderPickerCell.swift, ShareExtension/ShareFolderPickerCell.swift,
@ -353,7 +363,6 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet; isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = ( membershipExceptions = (
/Localized/ShareExtension/ShareViewController.xib, /Localized/ShareExtension/ShareViewController.xib,
AppDefaults.swift,
ShareExtension/icon.icns, ShareExtension/icon.icns,
ShareExtension/ShareViewController.swift, ShareExtension/ShareViewController.swift,
); );
@ -412,7 +421,10 @@
84C1ECED2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { 84C1ECED2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet; isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = ( membershipExceptions = (
AppDefaultsKey.swift, Defaults/AppDefaults.swift,
Defaults/ArticleTextSize.swift,
Defaults/FontSize.swift,
Defaults/UserInterfaceColorPalette.swift,
Extensions/IconImage.swift, Extensions/IconImage.swift,
"Extensions/Node-Extensions.swift", "Extensions/Node-Extensions.swift",
ShareExtension/ExtensionContainers.swift, ShareExtension/ExtensionContainers.swift,
@ -446,15 +458,17 @@
84C1ECF02CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { 84C1ECF02CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet; isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = ( membershipExceptions = (
AppDefaultsKey.swift, Defaults/AppDefaults.swift,
ArticleRendering/ArticleTextSize.swift, Defaults/ArticleTextSize.swift,
Defaults/FontSize.swift,
Defaults/RefreshInterval.swift,
Defaults/UserInterfaceColorPalette.swift,
ShareExtension/ExtensionContainers.swift, ShareExtension/ExtensionContainers.swift,
ShareExtension/ExtensionContainersFile.swift, ShareExtension/ExtensionContainersFile.swift,
ShareExtension/ExtensionFeedAddRequest.swift, ShareExtension/ExtensionFeedAddRequest.swift,
ShareExtension/ExtensionFeedAddRequestFile.swift, ShareExtension/ExtensionFeedAddRequestFile.swift,
ShareExtension/SafariExt.js, ShareExtension/SafariExt.js,
ShareExtension/ShareDefaultContainer.swift, ShareExtension/ShareDefaultContainer.swift,
Timer/RefreshInterval.swift,
); );
target = 510C415B24E5CDE3008226FD /* NetNewsWire Share Extension */; target = 510C415B24E5CDE3008226FD /* NetNewsWire Share Extension */;
}; };
@ -500,7 +514,7 @@
84BAE0AA2CE1C4D500402E69 /* Mac */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (84BAE1402CE1C4D500402E69 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84BAE1412CE1C4D500402E69 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, 84BAE1422CE1C4D500402E69 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84BAE1432CE1C4D500402E69 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Mac; sourceTree = "<group>"; }; 84BAE0AA2CE1C4D500402E69 /* Mac */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (84BAE1402CE1C4D500402E69 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84BAE1412CE1C4D500402E69 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, 84BAE1422CE1C4D500402E69 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84BAE1432CE1C4D500402E69 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Mac; sourceTree = "<group>"; };
84C1EB5E2CDFE18E00C7456A /* xcconfig */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (84C1EB602CDFE18E00C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (common, ); path = xcconfig; sourceTree = "<group>"; }; 84C1EB5E2CDFE18E00C7456A /* xcconfig */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (84C1EB602CDFE18E00C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (common, ); path = xcconfig; sourceTree = "<group>"; };
84C1EB7C2CDFE31700C7456A /* Tests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (84C1EB952CDFE31700C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1EB962CDFE31700C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Tests; sourceTree = "<group>"; }; 84C1EB7C2CDFE31700C7456A /* Tests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (84C1EB952CDFE31700C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1EB962CDFE31700C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Tests; sourceTree = "<group>"; };
84C1EC192CDFE49100C7456A /* Shared */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (84C1ECEB2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1ECEC2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1ECED2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1ECEE2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1ECEF2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1ECF02CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Shared; sourceTree = "<group>"; }; 84C1EC192CDFE49100C7456A /* Shared */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (84C1ECEB2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1ECEC2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 848D19082D46D78E00DD0819 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1ECED2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1ECEE2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1ECEF2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 84C1ECF02CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Shared; sourceTree = "<group>"; };
84C1ED042CDFE50500C7456A /* Widget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (84C1ED132CDFE50500C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Widget; sourceTree = "<group>"; }; 84C1ED042CDFE50500C7456A /* Widget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (84C1ED132CDFE50500C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Widget; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */ /* End PBXFileSystemSynchronizedRootGroup section */

View File

@ -1,60 +0,0 @@
//
// AppDefaultsKey.swift
// NetNewsWire
//
// Created by Brent Simmons on 1/25/25.
// Copyright © 2025 Ranchero Software. All rights reserved.
//
import Foundation
struct AppDefaultsKey {
static let firstRunDate = "firstRunDate"
static let lastImageCacheFlushDate = "lastImageCacheFlushDate"
static let timelineGroupByFeed = "timelineGroupByFeed"
static let timelineSortDirection = "timelineSortDirection"
static let addFeedAccountID = "addFeedAccountID"
static let addFeedFolderName = "addFeedFolderName"
static let addFolderAccountID = "addFolderAccountID"
static let currentThemeName = "currentThemeName"
static let articleContentJavascriptEnabled = "articleContentJavascriptEnabled"
#if os(macOS)
static let windowState = "windowState"
static let sidebarFontSize = "sidebarFontSize"
static let timelineFontSize = "timelineFontSize"
static let detailFontSize = "detailFontSize"
static let openInBrowserInBackground = "openInBrowserInBackground"
static let subscribeToFeedsInDefaultBrowser = "subscribeToFeedsInDefaultBrowser"
static let articleTextSize = "articleTextSize"
static let refreshInterval = "refreshInterval"
static let importOPMLAccountID = "importOPMLAccountID"
static let exportOPMLAccountID = "exportOPMLAccountID"
static let defaultBrowserID = "defaultBrowserID"
// Hidden prefs
static let showDebugMenu = "ShowDebugMenu"
static let timelineShowsSeparators = "CorreiaSeparators"
static let showTitleOnMainWindow = "KafasisTitleMode"
static let feedDoubleClickMarkAsRead = "GruberFeedDoubleClickMarkAsRead"
static let suppressSyncOnLaunch = "DevroeSuppressSyncOnLaunch"
static let webInspectorEnabled = "WebInspectorEnabled"
static let webInspectorStartsAttached = "__WebInspectorPageGroupLevel1__.WebKit2InspectorStartsAttached"
#elseif os(iOS)
static let userInterfaceColorPalette = "userInterfaceColorPalette"
static let refreshClearsReadArticles = "refreshClearsReadArticles"
static let timelineNumberOfLines = "timelineNumberOfLines"
static let timelineIconDimension = "timelineIconSize"
static let articleFullscreenAvailable = "articleFullscreenAvailable"
static let articleFullscreenEnabled = "articleFullscreenEnabled"
static let confirmMarkAllAsRead = "confirmMarkAllAsRead"
static let lastRefresh = "lastRefresh"
static let useSystemBrowser = "useSystemBrowser"
#endif
}

View File

@ -227,7 +227,7 @@ private extension ArticleRenderer {
d["body"] = body d["body"] = body
#if os(macOS) #if os(macOS)
d["text_size_class"] = AppDefaults.shared.articleTextSize.cssClass d["text_size_class"] = AppDefaults.articleTextSize.cssClass
#endif #endif
var components = URLComponents() var components = URLComponents()

View File

@ -46,7 +46,7 @@ private extension WebViewConfiguration {
static var webpagePreferences: WKWebpagePreferences { static var webpagePreferences: WKWebpagePreferences {
let preferences = WKWebpagePreferences() let preferences = WKWebpagePreferences()
preferences.allowsContentJavaScript = AppDefaults.shared.isArticleContentJavascriptEnabled preferences.allowsContentJavaScript = AppDefaults.isArticleContentJavascriptEnabled
return preferences return preferences
} }

View File

@ -26,11 +26,11 @@ final class ArticleThemesManager: NSObject, NSFilePresenter {
var currentThemeName: String { var currentThemeName: String {
get { get {
return AppDefaults.shared.currentThemeName ?? AppDefaults.defaultThemeName return AppDefaults.currentThemeName ?? AppDefaults.defaultThemeName
} }
set { set {
if newValue != currentThemeName { if newValue != currentThemeName {
AppDefaults.shared.currentThemeName = newValue AppDefaults.currentThemeName = newValue
updateThemeNames() updateThemeNames()
updateCurrentTheme() updateCurrentTheme()
} }

View File

@ -0,0 +1,662 @@
//
// AppDefaults.swift
// NetNewsWire
//
// Created by Brent Simmons on 1/25/25.
// Copyright © 2025 Ranchero Software. All rights reserved.
//
import Foundation
#if os(macOS)
import AppKit
#endif
struct AppDefaults {
enum Key: String {
case firstRunDate
case lastImageCacheFlushDate
case timelineGroupByFeed
case timelineSortDirection
case addFeedAccountID
case addFeedFolderName
case addFolderAccountID
case currentThemeName
case articleContentJavascriptEnabled
#if os(macOS)
case windowState
case sidebarFontSize
case timelineFontSize
case detailFontSize
case openInBrowserInBackground
case subscribeToFeedsInDefaultBrowser
case articleTextSize
case refreshInterval
case importOPMLAccountID
case exportOPMLAccountID
case defaultBrowserID
case webInspectorEnabled = "WebInspectorEnabled"
case webInspectorStartsAttached = "__WebInspectorPageGroupLevel1__.WebKit2InspectorStartsAttached"
// Hidden prefs
case showDebugMenu
case timelineShowsSeparators = "CorreiaSeparators"
case showTitleOnMainWindow = "KafasisTitleMode"
case feedDoubleClickMarkAsRead = "GruberFeedDoubleClickMarkAsRead"
case suppressSyncOnLaunch = "DevroeSuppressSyncOnLaunch"
#elseif os(iOS)
case userInterfaceColorPalette
case refreshClearsReadArticles
case timelineNumberOfLines
case timelineIconDimension = "timelineIconSize"
case articleFullscreenAvailable
case articleFullscreenEnabled
case confirmMarkAllAsRead
case lastRefresh
case useSystemBrowser
#endif
}
static let defaultThemeName = "Default"
static let isDeveloperBuild: Bool = {
if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" {
return true
}
return false
}()
static let isFirstRun: Bool = {
if firstRunDate == nil {
firstRunDate = Date()
return true
}
return false
}()
#if os(macOS)
static func registerDefaults() {
#if DEBUG
let showDebugMenu = true
#else
let showDebugMenu = false
#endif
let defaults: [String: Any] = [
Key.sidebarFontSize.rawValue: FontSize.medium.rawValue,
Key.timelineFontSize.rawValue: FontSize.medium.rawValue,
Key.detailFontSize.rawValue: FontSize.medium.rawValue,
Key.timelineSortDirection.rawValue: ComparisonResult.orderedDescending.rawValue,
Key.timelineGroupByFeed.rawValue: false,
Key.refreshInterval.rawValue: RefreshInterval.everyHour.rawValue,
Key.showDebugMenu.rawValue: showDebugMenu,
Key.currentThemeName.rawValue: Self.defaultThemeName,
Key.articleContentJavascriptEnabled.rawValue: true,
"NSScrollViewShouldScrollUnderTitlebar": false
]
UserDefaults.standard.register(defaults: defaults)
}
#elseif os(iOS)
static func registerDefaults() {
// TODO: migrate all (or as many as possible) out of shared
let sharedDefaults: [String: Any] = [
Key.userInterfaceColorPalette.rawValue: UserInterfaceColorPalette.automatic.rawValue,
Key.timelineGroupByFeed.rawValue: false,
Key.refreshClearsReadArticles.rawValue: false,
Key.timelineNumberOfLines.rawValue: 2,
Key.timelineIconDimension.rawValue: IconSize.medium.rawValue,
Key.timelineSortDirection.rawValue: ComparisonResult.orderedDescending.rawValue,
Key.articleFullscreenAvailable.rawValue: false,
Key.articleFullscreenEnabled.rawValue: false,
Key.confirmMarkAllAsRead.rawValue: true
]
appGroupStorage.register(defaults: sharedDefaults)
let defaults: [String: Any] = [
Key.currentThemeName.rawValue: Self.defaultThemeName,
Key.articleContentJavascriptEnabled.rawValue: true
]
UserDefaults.standard.register(defaults: defaults)
}
#endif
static var addFeedAccountID: String? {
get {
string(key: .addFeedAccountID)
}
set {
setString(newValue, key: .addFeedAccountID)
}
}
static var addFeedFolderName: String? {
get {
string(key: .addFeedFolderName)
}
set {
setString(newValue, key: .addFeedFolderName)
}
}
static var addFolderAccountID: String? {
get {
string(key: .addFolderAccountID)
}
set {
setString(newValue, key: .addFolderAccountID)
}
}
static var currentThemeName: String? {
get {
string(key: .currentThemeName)
}
set {
setString(newValue, key: .currentThemeName)
}
}
static var isArticleContentJavascriptEnabled: Bool {
get {
bool(key: .articleContentJavascriptEnabled)
}
set {
setBool(newValue, key: .articleContentJavascriptEnabled)
}
}
#if os(macOS)
static var timelineGroupByFeed: Bool {
get {
bool(key: .timelineGroupByFeed)
}
set {
setBool(newValue, key: .timelineGroupByFeed)
}
}
#elseif os(iOS)
static var timelineGroupByFeed: Bool {
// TODO: migrate to not shared
get {
sharedBool(key: .timelineGroupByFeed)
}
set {
setSharedBool(newValue, key: .timelineGroupByFeed)
}
}
#endif
#if os(macOS)
static var timelineSortDirection: ComparisonResult {
get {
sortDirection(key: .timelineSortDirection)
}
set {
setSortDirection(newValue, key: .timelineSortDirection)
}
}
#else
static var timelineSortDirection: ComparisonResult {
// TODO: migrate to not shared
get {
sharedSortDirection(key: .timelineSortDirection)
}
set {
setSharedSortDirection(newValue, key: .timelineSortDirection)
}
}
#endif
#if os(macOS)
static var lastImageCacheFlushDate: Date? {
get {
date(key: .lastImageCacheFlushDate)
}
set {
setDate(newValue, key: .lastImageCacheFlushDate)
}
}
#else
static var lastImageCacheFlushDate: Date? {
// TODO: migrate to not shared
get {
sharedDate(key: .lastImageCacheFlushDate)
}
set {
setSharedDate(newValue, key: .lastImageCacheFlushDate)
}
}
#endif
}
// MARK: - Mac-only Defaults
#if os(macOS)
extension AppDefaults {
static var windowState: [AnyHashable: Any]? {
get {
UserDefaults.standard.object(forKey: Key.windowState.rawValue) as? [AnyHashable: Any]
}
set {
UserDefaults.standard.set(newValue, forKey: Key.windowState.rawValue)
}
}
static var sidebarFontSize: FontSize {
get {
fontSize(key: .sidebarFontSize)
}
set {
setFontSize(newValue, key: .sidebarFontSize)
}
}
static var timelineFontSize: FontSize {
get {
fontSize(key: .timelineFontSize)
}
set {
setFontSize(newValue, key: .timelineFontSize)
}
}
static var detailFontSize: FontSize {
get {
fontSize(key: .detailFontSize)
}
set {
setFontSize(newValue, key: .detailFontSize)
}
}
static var importOPMLAccountID: String? {
get {
string(key: .importOPMLAccountID)
}
set {
setString(newValue, key: .importOPMLAccountID)
}
}
static var exportOPMLAccountID: String? {
get {
string(key: .exportOPMLAccountID)
}
set {
setString(newValue, key: .exportOPMLAccountID)
}
}
static var defaultBrowserID: String? {
get {
string(key: .defaultBrowserID)
}
set {
setString(newValue, key: .defaultBrowserID)
}
}
static var refreshInterval: RefreshInterval {
get {
let rawValue = int(key: .refreshInterval)
return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour
}
set {
setInt(newValue.rawValue, key: .refreshInterval)
}
}
static var openInBrowserInBackground: Bool {
get {
bool(key: .openInBrowserInBackground)
}
set {
setBool(newValue, key: .openInBrowserInBackground)
}
}
/// Shared with Subscribe to Feed Safari extension.
static var subscribeToFeedsInDefaultBrowser: Bool {
get {
sharedBool(key: .subscribeToFeedsInDefaultBrowser)
}
set {
setSharedBool(newValue, key: .subscribeToFeedsInDefaultBrowser)
}
}
static var articleTextSize: ArticleTextSize {
get {
let rawValue = int(key: .articleTextSize)
return ArticleTextSize(rawValue: rawValue) ?? ArticleTextSize.large
}
set {
setInt(newValue.rawValue, key: .articleTextSize)
}
}
static var webInspectorEnabled: Bool {
get {
bool(key: .webInspectorEnabled)
}
set {
setBool(newValue, key: .webInspectorEnabled)
}
}
static var webInspectorStartsAttached: Bool {
get {
bool(key: .webInspectorStartsAttached)
}
set {
setBool(newValue, key: .webInspectorStartsAttached)
}
}
private static let smallestFontSizeRawValue = FontSize.small.rawValue
private static let largestFontSizeRawValue = FontSize.veryLarge.rawValue
static func fontSize(key: Key) -> FontSize {
// Punted till after 1.0.
return .medium
// var rawFontSize = int(for: key)
// if rawFontSize < smallestFontSizeRawValue {
// rawFontSize = smallestFontSizeRawValue
// }
// if rawFontSize > largestFontSizeRawValue {
// rawFontSize = largestFontSizeRawValue
// }
// return FontSize(rawValue: rawFontSize)!
}
static func setFontSize(_ fontSize: FontSize, key: Key) {
setInt(fontSize.rawValue, key: key)
}
static func actualFontSize(for fontSize: FontSize) -> CGFloat {
switch fontSize {
case .small:
return NSFont.systemFontSize
case .medium:
return actualFontSize(for: .small) + 1.0
case .large:
return actualFontSize(for: .medium) + 4.0
case .veryLarge:
return actualFontSize(for: .large) + 8.0
}
}
// MARK: - Hidden prefs
static var showTitleOnMainWindow: Bool {
bool(key: .showTitleOnMainWindow)
}
static var showDebugMenu: Bool {
bool(key: .showDebugMenu)
}
static var suppressSyncOnLaunch: Bool {
bool(key: .suppressSyncOnLaunch)
}
static var timelineShowsSeparators: Bool {
bool(key: .timelineShowsSeparators)
}
static var feedDoubleClickMarkAsRead: Bool {
bool(key: .feedDoubleClickMarkAsRead)
}
}
#endif
// MARK: - iOS-only Defaults
#if os(iOS)
extension AppDefaults {
static var userInterfaceColorPalette: UserInterfaceColorPalette {
// TODO: migrate to not shared
get {
if let result = UserInterfaceColorPalette(rawValue: sharedInt(key: .userInterfaceColorPalette)) {
return result
}
return .automatic
}
set {
setSharedInt(newValue.rawValue, key: .userInterfaceColorPalette)
}
}
static var refreshClearsReadArticles: Bool {
// TODO: migrate to not shared
get {
sharedBool(key: .refreshClearsReadArticles)
}
set {
setSharedBool(newValue, key: .refreshClearsReadArticles)
}
}
static var useSystemBrowser: Bool {
get {
bool(key: .useSystemBrowser)
}
set {
setBool(newValue, key: .useSystemBrowser)
}
}
static var timelineIconSize: IconSize {
// TODO: migrate to not shared
get {
let rawValue = sharedInt(key: .timelineIconDimension)
return IconSize(rawValue: rawValue) ?? IconSize.medium
}
set {
setSharedInt(newValue.rawValue, key: .timelineIconDimension)
}
}
static var articleFullscreenAvailable: Bool {
// TODO: migrate to not shared
get {
sharedBool(key: .articleFullscreenAvailable)
}
set {
setSharedBool(newValue, key: .articleFullscreenAvailable)
}
}
static var articleFullscreenEnabled: Bool {
// TODO: migrate to not shared
get {
sharedBool(key: .articleFullscreenEnabled)
}
set {
setSharedBool(newValue, key: .articleFullscreenEnabled)
}
}
static var logicalArticleFullscreenEnabled: Bool {
articleFullscreenAvailable && articleFullscreenEnabled
}
static var confirmMarkAllAsRead: Bool {
// TODO: migrate to not shared
get {
sharedBool(key: .confirmMarkAllAsRead)
}
set {
setSharedBool(newValue, key: .confirmMarkAllAsRead)
}
}
static var lastRefresh: Date? {
// TODO: migrate to not shared
get {
sharedDate(key: .lastRefresh)
}
set {
setSharedDate(newValue, key: .lastRefresh)
}
}
static var timelineNumberOfLines: Int {
// TODO: migrate to not shared
get {
sharedInt(key: .timelineNumberOfLines)
}
set {
setSharedInt(newValue, key: .timelineNumberOfLines)
}
}
}
#endif
// MARK: - Private
private extension AppDefaults {
#if os(macOS)
static var firstRunDate: Date? {
get {
date(key: .firstRunDate)
}
set {
setDate(newValue, key: .firstRunDate)
}
}
#elseif os(iOS)
static var firstRunDate: Date? {
get {
sharedDate(key: .firstRunDate)
}
set {
setSharedDate(newValue, key: .firstRunDate)
}
}
#endif
static func bool(key: Key) -> Bool {
UserDefaults.standard.bool(forKey: key.rawValue)
}
static func setBool(_ flag: Bool, key: Key) {
UserDefaults.standard.set(flag, forKey: key.rawValue)
}
static func int(key: Key) -> Int {
UserDefaults.standard.integer(forKey: key.rawValue)
}
static func setInt(_ x: Int, key: Key) {
UserDefaults.standard.set(x, forKey: key.rawValue)
}
static func date(key: Key) -> Date? {
UserDefaults.standard.object(forKey: key.rawValue) as? Date
}
static func setDate(_ date: Date?, key: Key) {
UserDefaults.standard.set(date, forKey: key.rawValue)
}
static func string(key: Key) -> String? {
return UserDefaults.standard.string(forKey: key.rawValue)
}
static func setString(_ value: String?, key: Key) {
UserDefaults.standard.set(value, forKey: key.rawValue)
}
static func sortDirection(key: Key) -> ComparisonResult {
let rawInt = int(key: key)
if rawInt == ComparisonResult.orderedAscending.rawValue {
return .orderedAscending
}
return .orderedDescending
}
static func setSortDirection(_ value: ComparisonResult, key: Key) {
if value == .orderedAscending {
setInt(ComparisonResult.orderedAscending.rawValue, key: key)
} else {
setInt(ComparisonResult.orderedDescending.rawValue, key: key)
}
}
}
// MARK: - App Group Storage
// These are for preferences that are shared between the app and extensions and widgets.
// These are to be used *only* for preferences for that are actually shared, which should be rare.
private extension AppDefaults {
static var appGroupStorage: UserDefaults = {
#if os(macOS)
let appGroupSuiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
#elseif os(iOS)
let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String
let appGroupSuiteName = "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)"
#endif
return UserDefaults(suiteName: appGroupSuiteName)!
}()
static func sharedBool(key: Key) -> Bool {
appGroupStorage.bool(forKey: key.rawValue)
}
static func setSharedBool(_ flag: Bool, key: Key) {
appGroupStorage.set(flag, forKey: key.rawValue)
}
#if os(iOS)
static func sharedInt(key: Key) -> Int {
appGroupStorage.integer(forKey: key.rawValue)
}
static func setSharedInt(_ x: Int, key: Key) {
appGroupStorage.set(x, forKey: key.rawValue)
}
static func sharedDate(key: Key) -> Date? {
appGroupStorage.object(forKey: key.rawValue) as? Date
}
static func setSharedDate(_ date: Date?, key: Key) {
appGroupStorage.set(date, forKey: key.rawValue)
}
static func sharedSortDirection(key: Key) -> ComparisonResult {
let rawInt = sharedInt(key: key)
if rawInt == ComparisonResult.orderedAscending.rawValue {
return .orderedAscending
}
return .orderedDescending
}
static func setSharedSortDirection(_ value: ComparisonResult, key: Key) {
if value == .orderedAscending {
setSharedInt(ComparisonResult.orderedAscending.rawValue, key: key)
} else {
setSharedInt(ComparisonResult.orderedDescending.rawValue, key: key)
}
}
#endif
}

View File

@ -2,8 +2,8 @@
// ArticleTextSize.swift // ArticleTextSize.swift
// NetNewsWire // NetNewsWire
// //
// Created by Maurice Parker on 11/3/20. // Created by Brent Simmons on 1/26/25.
// Copyright © 2020 Ranchero Software. All rights reserved. // Copyright © 2025 Ranchero Software. All rights reserved.
// //
import Foundation import Foundation
@ -46,5 +46,4 @@ enum ArticleTextSize: Int, CaseIterable, Identifiable {
return NSLocalizedString("Extra Extra Large", comment: "XX-Large") return NSLocalizedString("Extra Extra Large", comment: "XX-Large")
} }
} }
} }

View File

@ -0,0 +1,16 @@
//
// FontSize.swift
// NetNewsWire
//
// Created by Brent Simmons on 1/26/25.
// Copyright © 2025 Ranchero Software. All rights reserved.
//
import Foundation
enum FontSize: Int {
case small = 0
case medium = 1
case large = 2
case veryLarge = 3
}

View File

@ -56,5 +56,4 @@ enum RefreshInterval: Int, CaseIterable, Identifiable {
return NSLocalizedString("Every 8 Hours", comment: "Every 8 Hours") return NSLocalizedString("Every 8 Hours", comment: "Every 8 Hours")
} }
} }
} }

View File

@ -0,0 +1,26 @@
//
// UserInterfaceColorPalette.swift
// NetNewsWire
//
// Created by Brent Simmons on 1/26/25.
// Copyright © 2025 Ranchero Software. All rights reserved.
//
import Foundation
enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable {
case automatic = 0
case light = 1
case dark = 2
var description: String {
switch self {
case .automatic:
return NSLocalizedString("Automatic", comment: "Automatic")
case .light:
return NSLocalizedString("Light", comment: "Light")
case .dark:
return NSLocalizedString("Dark", comment: "Dark")
}
}
}

View File

@ -13,8 +13,8 @@ struct AddFeedDefaultContainer {
static var defaultContainer: Container? { static var defaultContainer: Container? {
if let accountID = AppDefaults.shared.addFeedAccountID, let account = AccountManager.shared.activeAccounts.first(where: { $0.accountID == accountID }) { if let accountID = AppDefaults.addFeedAccountID, let account = AccountManager.shared.activeAccounts.first(where: { $0.accountID == accountID }) {
if let folderName = AppDefaults.shared.addFeedFolderName, let folder = account.existingFolder(withDisplayName: folderName) { if let folderName = AppDefaults.addFeedFolderName, let folder = account.existingFolder(withDisplayName: folderName) {
return folder return folder
} else { } else {
return substituteContainerIfNeeded(account: account) return substituteContainerIfNeeded(account: account)
@ -28,11 +28,11 @@ struct AddFeedDefaultContainer {
} }
static func saveDefaultContainer(_ container: Container) { static func saveDefaultContainer(_ container: Container) {
AppDefaults.shared.addFeedAccountID = container.account?.accountID AppDefaults.addFeedAccountID = container.account?.accountID
if let folder = container as? Folder { if let folder = container as? Folder {
AppDefaults.shared.addFeedFolderName = folder.nameForDisplay AppDefaults.addFeedFolderName = folder.nameForDisplay
} else { } else {
AppDefaults.shared.addFeedFolderName = nil AppDefaults.addFeedFolderName = nil
} }
} }

View File

@ -16,8 +16,8 @@ struct CacheCleaner {
static func purgeIfNecessary() { static func purgeIfNecessary() {
guard let flushDate = AppDefaults.shared.lastImageCacheFlushDate else { guard let flushDate = AppDefaults.lastImageCacheFlushDate else {
AppDefaults.shared.lastImageCacheFlushDate = Date() AppDefaults.lastImageCacheFlushDate = Date()
return return
} }
@ -42,7 +42,7 @@ struct CacheCleaner {
} }
} }
AppDefaults.shared.lastImageCacheFlushDate = Date() AppDefaults.lastImageCacheFlushDate = Date()
} }
} }

View File

@ -12,8 +12,8 @@ struct ShareDefaultContainer {
static func defaultContainer(containers: ExtensionContainers) -> ExtensionContainer? { static func defaultContainer(containers: ExtensionContainers) -> ExtensionContainer? {
if let accountID = AppDefaults.shared.addFeedAccountID, let account = containers.accounts.first(where: { $0.accountID == accountID }) { if let accountID = AppDefaults.addFeedAccountID, let account = containers.accounts.first(where: { $0.accountID == accountID }) {
if let folderName = AppDefaults.shared.addFeedFolderName, let folder = account.folders.first(where: { $0.name == folderName }) { if let folderName = AppDefaults.addFeedFolderName, let folder = account.folders.first(where: { $0.name == folderName }) {
return folder return folder
} else { } else {
return substituteContainerIfNeeded(account: account) return substituteContainerIfNeeded(account: account)
@ -27,11 +27,11 @@ struct ShareDefaultContainer {
} }
static func saveDefaultContainer(_ container: ExtensionContainer) { static func saveDefaultContainer(_ container: ExtensionContainer) {
AppDefaults.shared.addFeedAccountID = container.accountID AppDefaults.addFeedAccountID = container.accountID
if let folder = container as? ExtensionFolder { if let folder = container as? ExtensionFolder {
AppDefaults.shared.addFeedFolderName = folder.name AppDefaults.addFeedFolderName = folder.name
} else { } else {
AppDefaults.shared.addFeedFolderName = nil AppDefaults.addFeedFolderName = nil
} }
} }

View File

@ -20,7 +20,7 @@ final class AccountRefreshTimer {
func fireOldTimer() { func fireOldTimer() {
if let timer = internalTimer { if let timer = internalTimer {
if timer.fireDate < Date() { if timer.fireDate < Date() {
if AppDefaults.shared.refreshInterval != .manually { if AppDefaults.refreshInterval != .manually {
timedRefresh(nil) timedRefresh(nil)
} }
} }
@ -42,7 +42,7 @@ final class AccountRefreshTimer {
return return
} }
let refreshInterval = AppDefaults.shared.refreshInterval let refreshInterval = AppDefaults.refreshInterval
if refreshInterval == .manually { if refreshInterval == .manually {
invalidate() invalidate()
return return

View File

@ -25,7 +25,7 @@ final class AddFolderViewController: UITableViewController {
private var accounts: [Account]! { private var accounts: [Account]! {
didSet { didSet {
if let predefinedAccount = accounts.first(where: { $0.accountID == AppDefaults.shared.addFolderAccountID }) { if let predefinedAccount = accounts.first(where: { $0.accountID == AppDefaults.addFolderAccountID }) {
selectedAccount = predefinedAccount selectedAccount = predefinedAccount
} else { } else {
selectedAccount = accounts[0] selectedAccount = accounts[0]
@ -67,7 +67,7 @@ final class AddFolderViewController: UITableViewController {
} }
private func didSelect(_ account: Account) { private func didSelect(_ account: Account) {
AppDefaults.shared.addFolderAccountID = account.accountID AppDefaults.addFolderAccountID = account.accountID
selectedAccount = account selectedAccount = account
} }

View File

@ -1,293 +0,0 @@
//
// AppDefaults.swift
// NetNewsWire
//
// Created by Brent Simmons on 9/22/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import UIKit
enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable {
case automatic = 0
case light = 1
case dark = 2
var description: String {
switch self {
case .automatic:
return NSLocalizedString("Automatic", comment: "Automatic")
case .light:
return NSLocalizedString("Light", comment: "Light")
case .dark:
return NSLocalizedString("Dark", comment: "Dark")
}
}
}
final class AppDefaults {
static let defaultThemeName = "Default"
static let shared = AppDefaults()
private init() {}
static var store: UserDefaults = {
let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String
let suiteName = "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)"
return UserDefaults.init(suiteName: suiteName)!
}()
let isDeveloperBuild: Bool = {
if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" {
return true
}
return false
}()
let isFirstRun: Bool = {
if AppDefaults.store.object(forKey: AppDefaultsKey.firstRunDate) as? Date == nil {
firstRunDate = Date()
return true
}
return false
}()
static var userInterfaceColorPalette: UserInterfaceColorPalette {
get {
if let result = UserInterfaceColorPalette(rawValue: int(for: AppDefaultsKey.userInterfaceColorPalette)) {
return result
}
return .automatic
}
set {
setInt(for: AppDefaultsKey.userInterfaceColorPalette, newValue.rawValue)
}
}
var addFeedAccountID: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.addFeedAccountID)
}
set {
AppDefaults.setString(for: AppDefaultsKey.addFeedAccountID, newValue)
}
}
var addFeedFolderName: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.addFeedFolderName)
}
set {
AppDefaults.setString(for: AppDefaultsKey.addFeedFolderName, newValue)
}
}
var addFolderAccountID: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.addFolderAccountID)
}
set {
AppDefaults.setString(for: AppDefaultsKey.addFolderAccountID, newValue)
}
}
var useSystemBrowser: Bool {
get {
return UserDefaults.standard.bool(forKey: AppDefaultsKey.useSystemBrowser)
}
set {
UserDefaults.standard.setValue(newValue, forKey: AppDefaultsKey.useSystemBrowser)
}
}
var lastImageCacheFlushDate: Date? {
get {
return AppDefaults.date(for: AppDefaultsKey.lastImageCacheFlushDate)
}
set {
AppDefaults.setDate(for: AppDefaultsKey.lastImageCacheFlushDate, newValue)
}
}
var timelineGroupByFeed: Bool {
get {
return AppDefaults.bool(for: AppDefaultsKey.timelineGroupByFeed)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.timelineGroupByFeed, newValue)
}
}
var refreshClearsReadArticles: Bool {
get {
return AppDefaults.bool(for: AppDefaultsKey.refreshClearsReadArticles)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.refreshClearsReadArticles, newValue)
}
}
var timelineSortDirection: ComparisonResult {
get {
return AppDefaults.sortDirection(for: AppDefaultsKey.timelineSortDirection)
}
set {
AppDefaults.setSortDirection(for: AppDefaultsKey.timelineSortDirection, newValue)
}
}
var articleFullscreenAvailable: Bool {
get {
return AppDefaults.bool(for: AppDefaultsKey.articleFullscreenAvailable)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.articleFullscreenAvailable, newValue)
}
}
var articleFullscreenEnabled: Bool {
get {
return articleFullscreenAvailable && AppDefaults.bool(for: AppDefaultsKey.articleFullscreenEnabled)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.articleFullscreenEnabled, newValue)
}
}
var logicalArticleFullscreenEnabled: Bool {
articleFullscreenAvailable && articleFullscreenEnabled
}
var confirmMarkAllAsRead: Bool {
get {
return AppDefaults.bool(for: AppDefaultsKey.confirmMarkAllAsRead)
}
set {
AppDefaults.setBool(for: AppDefaultsKey.confirmMarkAllAsRead, newValue)
}
}
var lastRefresh: Date? {
get {
return AppDefaults.date(for: AppDefaultsKey.lastRefresh)
}
set {
AppDefaults.setDate(for: AppDefaultsKey.lastRefresh, newValue)
}
}
var timelineNumberOfLines: Int {
get {
return AppDefaults.int(for: AppDefaultsKey.timelineNumberOfLines)
}
set {
AppDefaults.setInt(for: AppDefaultsKey.timelineNumberOfLines, newValue)
}
}
var timelineIconSize: IconSize {
get {
let rawValue = AppDefaults.store.integer(forKey: AppDefaultsKey.timelineIconDimension)
return IconSize(rawValue: rawValue) ?? IconSize.medium
}
set {
AppDefaults.store.set(newValue.rawValue, forKey: AppDefaultsKey.timelineIconDimension)
}
}
var currentThemeName: String? {
get {
return AppDefaults.string(for: AppDefaultsKey.currentThemeName)
}
set {
AppDefaults.setString(for: AppDefaultsKey.currentThemeName, newValue)
}
}
var isArticleContentJavascriptEnabled: Bool {
get {
UserDefaults.standard.bool(forKey: AppDefaultsKey.articleContentJavascriptEnabled)
}
set {
UserDefaults.standard.set(newValue, forKey: AppDefaultsKey.articleContentJavascriptEnabled)
}
}
static func registerDefaults() {
let defaults: [String: Any] = [AppDefaultsKey.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue,
AppDefaultsKey.timelineGroupByFeed: false,
AppDefaultsKey.refreshClearsReadArticles: false,
AppDefaultsKey.timelineNumberOfLines: 2,
AppDefaultsKey.timelineIconDimension: IconSize.medium.rawValue,
AppDefaultsKey.timelineSortDirection: ComparisonResult.orderedDescending.rawValue,
AppDefaultsKey.articleFullscreenAvailable: false,
AppDefaultsKey.articleFullscreenEnabled: false,
AppDefaultsKey.confirmMarkAllAsRead: true,
AppDefaultsKey.currentThemeName: Self.defaultThemeName]
AppDefaults.store.register(defaults: defaults)
}
}
private extension AppDefaults {
static var firstRunDate: Date? {
get {
return date(for: AppDefaultsKey.firstRunDate)
}
set {
setDate(for: AppDefaultsKey.firstRunDate, newValue)
}
}
static func string(for key: String) -> String? {
return UserDefaults.standard.string(forKey: key)
}
static func setString(for key: String, _ value: String?) {
UserDefaults.standard.set(value, forKey: key)
}
static func bool(for key: String) -> Bool {
return AppDefaults.store.bool(forKey: key)
}
static func setBool(for key: String, _ flag: Bool) {
AppDefaults.store.set(flag, forKey: key)
}
static func int(for key: String) -> Int {
return AppDefaults.store.integer(forKey: key)
}
static func setInt(for key: String, _ x: Int) {
AppDefaults.store.set(x, forKey: key)
}
static func date(for key: String) -> Date? {
return AppDefaults.store.object(forKey: key) as? Date
}
static func setDate(for key: String, _ date: Date?) {
AppDefaults.store.set(date, forKey: key)
}
static func sortDirection(for key: String) -> ComparisonResult {
let rawInt = int(for: key)
if rawInt == ComparisonResult.orderedAscending.rawValue {
return .orderedAscending
}
return .orderedDescending
}
static func setSortDirection(for key: String, _ value: ComparisonResult) {
if value == .orderedAscending {
setInt(for: key, ComparisonResult.orderedAscending.rawValue)
} else {
setInt(for: key, ComparisonResult.orderedDescending.rawValue)
}
}
}

View File

@ -76,7 +76,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppDefaults.registerDefaults() AppDefaults.registerDefaults()
let isFirstRun = AppDefaults.shared.isFirstRun let isFirstRun = AppDefaults.isFirstRun
if isFirstRun { if isFirstRun {
logger.info("Is first run.") logger.info("Is first run.")
} }
@ -148,7 +148,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
} }
@objc func accountRefreshDidFinish(_ note: Notification) { @objc func accountRefreshDidFinish(_ note: Notification) {
AppDefaults.shared.lastRefresh = Date() AppDefaults.lastRefresh = Date()
} }
// MARK: - API // MARK: - API
@ -180,7 +180,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationC
extensionFeedAddRequestFile.resume() extensionFeedAddRequestFile.resume()
syncTimer?.update() syncTimer?.update()
if let lastRefresh = AppDefaults.shared.lastRefresh { if let lastRefresh = AppDefaults.lastRefresh {
if Date() > lastRefresh.addingTimeInterval(15 * 60) { if Date() > lastRefresh.addingTimeInterval(15 * 60) {
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
} else { } else {

View File

@ -149,7 +149,7 @@ final class ArticleViewController: UIViewController {
articleExtractorButton.buttonState = controller.articleExtractorButtonState articleExtractorButton.buttonState = controller.articleExtractorButtonState
self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil) self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil)
if AppDefaults.shared.logicalArticleFullscreenEnabled { if AppDefaults.logicalArticleFullscreenEnabled {
controller.hideBars() controller.hideBars()
} }
@ -165,7 +165,7 @@ final class ArticleViewController: UIViewController {
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
let hideToolbars = AppDefaults.shared.logicalArticleFullscreenEnabled let hideToolbars = AppDefaults.logicalArticleFullscreenEnabled
if hideToolbars { if hideToolbars {
currentWebViewController?.hideBars() currentWebViewController?.hideBars()
} else { } else {
@ -219,7 +219,7 @@ final class ArticleViewController: UIViewController {
starBarButtonItem.isEnabled = true starBarButtonItem.isEnabled = true
let permalinkPresent = article.preferredLink != nil let permalinkPresent = article.preferredLink != nil
articleExtractorButton.isEnabled = permalinkPresent && !AppDefaults.shared.isDeveloperBuild articleExtractorButton.isEnabled = permalinkPresent && !AppDefaults.isDeveloperBuild
actionBarButtonItem.isEnabled = permalinkPresent actionBarButtonItem.isEnabled = permalinkPresent
if article.status.read { if article.status.read {
@ -265,7 +265,7 @@ final class ArticleViewController: UIViewController {
@objc func willEnterForeground(_ note: Notification) { @objc func willEnterForeground(_ note: Notification) {
// The toolbar will come back on you if you don't hide it again // The toolbar will come back on you if you don't hide it again
if AppDefaults.shared.logicalArticleFullscreenEnabled { if AppDefaults.logicalArticleFullscreenEnabled {
currentWebViewController?.hideBars() currentWebViewController?.hideBars()
} }
} }

View File

@ -37,7 +37,7 @@ final class WebViewController: UIViewController {
private lazy var contextMenuInteraction = UIContextMenuInteraction(delegate: self) private lazy var contextMenuInteraction = UIContextMenuInteraction(delegate: self)
private var isFullScreenAvailable: Bool { private var isFullScreenAvailable: Bool {
return AppDefaults.shared.articleFullscreenAvailable && traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed return AppDefaults.articleFullscreenAvailable && traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed
} }
private lazy var articleIconSchemeHandler = ArticleIconSchemeHandler(delegate: self) private lazy var articleIconSchemeHandler = ArticleIconSchemeHandler(delegate: self)
private lazy var transition = ImageTransition(controller: self) private lazy var transition = ImageTransition(controller: self)
@ -197,7 +197,7 @@ final class WebViewController: UIViewController {
} }
func showBars() { func showBars() {
AppDefaults.shared.articleFullscreenEnabled = false AppDefaults.articleFullscreenEnabled = false
coordinator.showStatusBar() coordinator.showStatusBar()
topShowBarsViewConstraint?.constant = 0 topShowBarsViewConstraint?.constant = 0
bottomShowBarsViewConstraint?.constant = 0 bottomShowBarsViewConstraint?.constant = 0
@ -208,7 +208,7 @@ final class WebViewController: UIViewController {
func hideBars() { func hideBars() {
if isFullScreenAvailable { if isFullScreenAvailable {
AppDefaults.shared.articleFullscreenEnabled = true AppDefaults.articleFullscreenEnabled = true
coordinator.hideStatusBar() coordinator.hideStatusBar()
topShowBarsViewConstraint?.constant = -44.0 topShowBarsViewConstraint?.constant = -44.0
bottomShowBarsViewConstraint?.constant = 44.0 bottomShowBarsViewConstraint?.constant = 44.0
@ -271,7 +271,7 @@ final class WebViewController: UIViewController {
func openInAppBrowser() { func openInAppBrowser() {
guard let url = article?.preferredURL else { return } guard let url = article?.preferredURL else { return }
if AppDefaults.shared.useSystemBrowser { if AppDefaults.useSystemBrowser {
UIApplication.shared.open(url, options: [:]) UIApplication.shared.open(url, options: [:])
} else { } else {
openURLInSafariViewController(url) openURLInSafariViewController(url)
@ -381,7 +381,7 @@ extension WebViewController: WKNavigationDelegate {
let components = URLComponents(url: url, resolvingAgainstBaseURL: false) let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
if components?.scheme == "http" || components?.scheme == "https" { if components?.scheme == "http" || components?.scheme == "https" {
decisionHandler(.cancel) decisionHandler(.cancel)
if AppDefaults.shared.useSystemBrowser { if AppDefaults.useSystemBrowser {
UIApplication.shared.open(url, options: [:]) UIApplication.shared.open(url, options: [:])
} else { } else {
UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { didOpen in UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { didOpen in
@ -674,7 +674,7 @@ private extension WebViewController {
topShowBarsView.translatesAutoresizingMaskIntoConstraints = false topShowBarsView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(topShowBarsView) view.addSubview(topShowBarsView)
if AppDefaults.shared.logicalArticleFullscreenEnabled { if AppDefaults.logicalArticleFullscreenEnabled {
topShowBarsViewConstraint = view.topAnchor.constraint(equalTo: topShowBarsView.bottomAnchor, constant: -44.0) topShowBarsViewConstraint = view.topAnchor.constraint(equalTo: topShowBarsView.bottomAnchor, constant: -44.0)
} else { } else {
topShowBarsViewConstraint = view.topAnchor.constraint(equalTo: topShowBarsView.bottomAnchor, constant: 0.0) topShowBarsViewConstraint = view.topAnchor.constraint(equalTo: topShowBarsView.bottomAnchor, constant: 0.0)
@ -694,7 +694,7 @@ private extension WebViewController {
topShowBarsView.backgroundColor = .clear topShowBarsView.backgroundColor = .clear
bottomShowBarsView.translatesAutoresizingMaskIntoConstraints = false bottomShowBarsView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bottomShowBarsView) view.addSubview(bottomShowBarsView)
if AppDefaults.shared.logicalArticleFullscreenEnabled { if AppDefaults.logicalArticleFullscreenEnabled {
bottomShowBarsViewConstraint = view.bottomAnchor.constraint(equalTo: bottomShowBarsView.topAnchor, constant: 44.0) bottomShowBarsViewConstraint = view.bottomAnchor.constraint(equalTo: bottomShowBarsView.topAnchor, constant: 44.0)
} else { } else {
bottomShowBarsViewConstraint = view.bottomAnchor.constraint(equalTo: bottomShowBarsView.topAnchor, constant: 0.0) bottomShowBarsViewConstraint = view.bottomAnchor.constraint(equalTo: bottomShowBarsView.topAnchor, constant: 0.0)

View File

@ -30,7 +30,7 @@ struct MarkAsReadAlertController {
return return
} }
if AppDefaults.shared.confirmMarkAllAsRead { if AppDefaults.confirmMarkAllAsRead {
let alertController = MarkAsReadAlertController.alert(coordinator: coordinator, confirmTitle: confirmTitle, cancelCompletion: cancelCompletion, sourceType: sourceType) { _ in let alertController = MarkAsReadAlertController.alert(coordinator: coordinator, confirmTitle: confirmTitle, cancelCompletion: cancelCompletion, sourceType: sourceType) { _ in
completion() completion()
} }

View File

@ -90,8 +90,8 @@ final class TimelineViewController: UITableViewController, UndoableCommandRunner
tableView.dataSource = dataSource tableView.dataSource = dataSource
tableView.isPrefetchingEnabled = false tableView.isPrefetchingEnabled = false
numberOfTextLines = AppDefaults.shared.timelineNumberOfLines numberOfTextLines = AppDefaults.timelineNumberOfLines
iconSize = AppDefaults.shared.timelineIconSize iconSize = AppDefaults.timelineIconSize
resetEstimatedRowHeight() resetEstimatedRowHeight()
if let titleView = Bundle.main.loadNibNamed("MainTimelineTitleView", owner: self, options: nil)?[0] as? MainTimelineTitleView { if let titleView = Bundle.main.loadNibNamed("MainTimelineTitleView", owner: self, options: nil)?[0] as? MainTimelineTitleView {
@ -510,9 +510,9 @@ final class TimelineViewController: UITableViewController, UndoableCommandRunner
@objc func userDefaultsDidChange(_ note: Notification) { @objc func userDefaultsDidChange(_ note: Notification) {
DispatchQueue.main.async { DispatchQueue.main.async {
if self.numberOfTextLines != AppDefaults.shared.timelineNumberOfLines || self.iconSize != AppDefaults.shared.timelineIconSize { if self.numberOfTextLines != AppDefaults.timelineNumberOfLines || self.iconSize != AppDefaults.timelineIconSize {
self.numberOfTextLines = AppDefaults.shared.timelineNumberOfLines self.numberOfTextLines = AppDefaults.timelineNumberOfLines
self.iconSize = AppDefaults.shared.timelineIconSize self.iconSize = AppDefaults.timelineIconSize
self.resetEstimatedRowHeight() self.resetEstimatedRowHeight()
self.reloadAllVisibleCells() self.reloadAllVisibleCells()
} }

View File

@ -81,7 +81,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner {
var isTimelineViewControllerPending = false var isTimelineViewControllerPending = false
var isArticleViewControllerPending = false var isArticleViewControllerPending = false
private(set) var sortDirection = AppDefaults.shared.timelineSortDirection { private(set) var sortDirection = AppDefaults.timelineSortDirection {
didSet { didSet {
if sortDirection != oldValue { if sortDirection != oldValue {
sortParametersDidChange() sortParametersDidChange()
@ -89,7 +89,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner {
} }
} }
private(set) var groupByFeed = AppDefaults.shared.timelineGroupByFeed { private(set) var groupByFeed = AppDefaults.timelineGroupByFeed {
didSet { didSet {
if groupByFeed != oldValue { if groupByFeed != oldValue {
sortParametersDidChange() sortParametersDidChange()
@ -491,8 +491,8 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner {
} }
@objc func userDefaultsDidChange(_ note: Notification) { @objc func userDefaultsDidChange(_ note: Notification) {
self.sortDirection = AppDefaults.shared.timelineSortDirection self.sortDirection = AppDefaults.timelineSortDirection
self.groupByFeed = AppDefaults.shared.timelineGroupByFeed self.groupByFeed = AppDefaults.timelineGroupByFeed
} }
@objc func accountDidDownloadArticles(_ note: Notification) { @objc func accountDidDownloadArticles(_ note: Notification) {
@ -548,7 +548,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner {
if isReadFeedsFiltered { if isReadFeedsFiltered {
rebuildBackingStores() rebuildBackingStores()
} }
if isReadArticlesFiltered && (AppDefaults.shared.refreshClearsReadArticles || !conditional) { if isReadArticlesFiltered && (AppDefaults.refreshClearsReadArticles || !conditional) {
refreshTimeline(resetScroll: false) refreshTimeline(resetScroll: false)
} }
} }

View File

@ -30,7 +30,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
Task { @MainActor in Task { @MainActor in
// Ensure Feeds view shows on first run on iPad  otherwise the UI is empty. // Ensure Feeds view shows on first run on iPad  otherwise the UI is empty.
if UIDevice.current.userInterfaceIdiom == .pad && AppDefaults.shared.isFirstRun { if UIDevice.current.userInterfaceIdiom == .pad && AppDefaults.isFirstRun {
rootViewController.show(.primary) rootViewController.show(.primary)
} }
} }

View File

@ -131,7 +131,7 @@ final class AddAccountViewController: UITableViewController, AddAccountDismissDe
case AddAccountSections.icloud.rawValue: case AddAccountSections.icloud.rawValue:
cell.comboNameLabel?.text = AddAccountSections.icloud.sectionContent[indexPath.row].localizedAccountName() cell.comboNameLabel?.text = AddAccountSections.icloud.sectionContent[indexPath.row].localizedAccountName()
cell.comboImage?.image = AppAssets.image(for: AddAccountSections.icloud.sectionContent[indexPath.row]) cell.comboImage?.image = AppAssets.image(for: AddAccountSections.icloud.sectionContent[indexPath.row])
if AppDefaults.shared.isDeveloperBuild || AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit }) { if AppDefaults.isDeveloperBuild || AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit }) {
cell.isUserInteractionEnabled = false cell.isUserInteractionEnabled = false
cell.comboNameLabel?.isEnabled = false cell.comboNameLabel?.isEnabled = false
} }
@ -139,7 +139,7 @@ final class AddAccountViewController: UITableViewController, AddAccountDismissDe
cell.comboNameLabel?.text = AddAccountSections.web.sectionContent[indexPath.row].localizedAccountName() cell.comboNameLabel?.text = AddAccountSections.web.sectionContent[indexPath.row].localizedAccountName()
cell.comboImage?.image = AppAssets.image(for: AddAccountSections.web.sectionContent[indexPath.row]) cell.comboImage?.image = AppAssets.image(for: AddAccountSections.web.sectionContent[indexPath.row])
let type = AddAccountSections.web.sectionContent[indexPath.row] let type = AddAccountSections.web.sectionContent[indexPath.row]
if (type == .feedly || type == .inoreader) && AppDefaults.shared.isDeveloperBuild { if (type == .feedly || type == .inoreader) && AppDefaults.isDeveloperBuild {
cell.isUserInteractionEnabled = false cell.isUserInteractionEnabled = false
cell.comboNameLabel?.isEnabled = false cell.comboNameLabel?.isEnabled = false
} }

View File

@ -49,19 +49,19 @@ final class SettingsViewController: UITableViewController {
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
if AppDefaults.shared.timelineSortDirection == .orderedAscending { if AppDefaults.timelineSortDirection == .orderedAscending {
timelineSortOrderSwitch.isOn = true timelineSortOrderSwitch.isOn = true
} else { } else {
timelineSortOrderSwitch.isOn = false timelineSortOrderSwitch.isOn = false
} }
if AppDefaults.shared.timelineGroupByFeed { if AppDefaults.timelineGroupByFeed {
groupByFeedSwitch.isOn = true groupByFeedSwitch.isOn = true
} else { } else {
groupByFeedSwitch.isOn = false groupByFeedSwitch.isOn = false
} }
if AppDefaults.shared.refreshClearsReadArticles { if AppDefaults.refreshClearsReadArticles {
refreshClearsReadArticlesSwitch.isOn = true refreshClearsReadArticlesSwitch.isOn = true
} else { } else {
refreshClearsReadArticlesSwitch.isOn = false refreshClearsReadArticlesSwitch.isOn = false
@ -69,13 +69,13 @@ final class SettingsViewController: UITableViewController {
articleThemeDetailLabel.text = ArticleThemesManager.shared.currentTheme.name articleThemeDetailLabel.text = ArticleThemesManager.shared.currentTheme.name
if AppDefaults.shared.confirmMarkAllAsRead { if AppDefaults.confirmMarkAllAsRead {
confirmMarkAllAsReadSwitch.isOn = true confirmMarkAllAsReadSwitch.isOn = true
} else { } else {
confirmMarkAllAsReadSwitch.isOn = false confirmMarkAllAsReadSwitch.isOn = false
} }
if AppDefaults.shared.articleFullscreenAvailable { if AppDefaults.articleFullscreenAvailable {
showFullscreenArticlesSwitch.isOn = true showFullscreenArticlesSwitch.isOn = true
} else { } else {
showFullscreenArticlesSwitch.isOn = false showFullscreenArticlesSwitch.isOn = false
@ -83,7 +83,7 @@ final class SettingsViewController: UITableViewController {
colorPaletteDetailLabel.text = String(describing: AppDefaults.userInterfaceColorPalette) colorPaletteDetailLabel.text = String(describing: AppDefaults.userInterfaceColorPalette)
openLinksInNetNewsWire.isOn = !AppDefaults.shared.useSystemBrowser openLinksInNetNewsWire.isOn = !AppDefaults.useSystemBrowser
let buildLabel = NonIntrinsicLabel(frame: CGRect(x: 32.0, y: 0.0, width: 0.0, height: 0.0)) let buildLabel = NonIntrinsicLabel(frame: CGRect(x: 32.0, y: 0.0, width: 0.0, height: 0.0))
buildLabel.font = UIFont.systemFont(ofSize: 11.0) buildLabel.font = UIFont.systemFont(ofSize: 11.0)
@ -261,49 +261,49 @@ final class SettingsViewController: UITableViewController {
@IBAction func switchTimelineOrder(_ sender: Any) { @IBAction func switchTimelineOrder(_ sender: Any) {
if timelineSortOrderSwitch.isOn { if timelineSortOrderSwitch.isOn {
AppDefaults.shared.timelineSortDirection = .orderedAscending AppDefaults.timelineSortDirection = .orderedAscending
} else { } else {
AppDefaults.shared.timelineSortDirection = .orderedDescending AppDefaults.timelineSortDirection = .orderedDescending
} }
} }
@IBAction func switchGroupByFeed(_ sender: Any) { @IBAction func switchGroupByFeed(_ sender: Any) {
if groupByFeedSwitch.isOn { if groupByFeedSwitch.isOn {
AppDefaults.shared.timelineGroupByFeed = true AppDefaults.timelineGroupByFeed = true
} else { } else {
AppDefaults.shared.timelineGroupByFeed = false AppDefaults.timelineGroupByFeed = false
} }
} }
@IBAction func switchClearsReadArticles(_ sender: Any) { @IBAction func switchClearsReadArticles(_ sender: Any) {
if refreshClearsReadArticlesSwitch.isOn { if refreshClearsReadArticlesSwitch.isOn {
AppDefaults.shared.refreshClearsReadArticles = true AppDefaults.refreshClearsReadArticles = true
} else { } else {
AppDefaults.shared.refreshClearsReadArticles = false AppDefaults.refreshClearsReadArticles = false
} }
} }
@IBAction func switchConfirmMarkAllAsRead(_ sender: Any) { @IBAction func switchConfirmMarkAllAsRead(_ sender: Any) {
if confirmMarkAllAsReadSwitch.isOn { if confirmMarkAllAsReadSwitch.isOn {
AppDefaults.shared.confirmMarkAllAsRead = true AppDefaults.confirmMarkAllAsRead = true
} else { } else {
AppDefaults.shared.confirmMarkAllAsRead = false AppDefaults.confirmMarkAllAsRead = false
} }
} }
@IBAction func switchFullscreenArticles(_ sender: Any) { @IBAction func switchFullscreenArticles(_ sender: Any) {
if showFullscreenArticlesSwitch.isOn { if showFullscreenArticlesSwitch.isOn {
AppDefaults.shared.articleFullscreenAvailable = true AppDefaults.articleFullscreenAvailable = true
} else { } else {
AppDefaults.shared.articleFullscreenAvailable = false AppDefaults.articleFullscreenAvailable = false
} }
} }
@IBAction func switchBrowserPreference(_ sender: Any) { @IBAction func switchBrowserPreference(_ sender: Any) {
if openLinksInNetNewsWire.isOn { if openLinksInNetNewsWire.isOn {
AppDefaults.shared.useSystemBrowser = false AppDefaults.useSystemBrowser = false
} else { } else {
AppDefaults.shared.useSystemBrowser = true AppDefaults.useSystemBrowser = true
} }
} }

View File

@ -27,11 +27,11 @@ final class TimelineCustomizerViewController: UIViewController {
super.viewDidLoad() super.viewDidLoad()
iconSizeSliderContainerView.layer.cornerRadius = 10 iconSizeSliderContainerView.layer.cornerRadius = 10
iconSizeSlider.value = Float(AppDefaults.shared.timelineIconSize.rawValue) iconSizeSlider.value = Float(AppDefaults.timelineIconSize.rawValue)
iconSizeSlider.addTickMarks() iconSizeSlider.addTickMarks()
numberOfLinesSliderContainerView.layer.cornerRadius = 10 numberOfLinesSliderContainerView.layer.cornerRadius = 10
numberOfLinesSlider.value = Float(AppDefaults.shared.timelineNumberOfLines) numberOfLinesSlider.value = Float(AppDefaults.timelineNumberOfLines)
numberOfLinesSlider.addTickMarks() numberOfLinesSlider.addTickMarks()
} }
@ -48,12 +48,12 @@ final class TimelineCustomizerViewController: UIViewController {
@IBAction func iconSizeChanged(_ sender: Any) { @IBAction func iconSizeChanged(_ sender: Any) {
guard let iconSize = IconSize(rawValue: Int(iconSizeSlider.value.rounded())) else { return } guard let iconSize = IconSize(rawValue: Int(iconSizeSlider.value.rounded())) else { return }
AppDefaults.shared.timelineIconSize = iconSize AppDefaults.timelineIconSize = iconSize
updatePreview() updatePreview()
} }
@IBAction func numberOfLinesChanged(_ sender: Any) { @IBAction func numberOfLinesChanged(_ sender: Any) {
AppDefaults.shared.timelineNumberOfLines = Int(numberOfLinesSlider.value.rounded()) AppDefaults.timelineNumberOfLines = Int(numberOfLinesSlider.value.rounded())
updatePreview() updatePreview()
} }

View File

@ -92,8 +92,8 @@ private extension TimelinePreviewTableViewController {
feedName: "Feed Name", feedName: "Feed Name",
byline: nil, iconImage: iconImage, byline: nil, iconImage: iconImage,
showIcon: true, showIcon: true,
numberOfLines: AppDefaults.shared.timelineNumberOfLines, numberOfLines: AppDefaults.timelineNumberOfLines,
iconSize: AppDefaults.shared.timelineIconSize iconSize: AppDefaults.timelineIconSize
) )
} }
} }