NetNewsWire/Mac/AppDefaults.swift

440 lines
12 KiB
Swift

//
// 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: Sendable {
static let defaultThemeName = "Default"
static let shared = AppDefaults()
struct Key {
static let firstRunDate = "firstRunDate"
static let windowState = "windowState"
static let lastImageCacheFlushDate = "lastImageCacheFlushDate"
static let sidebarFontSize = "sidebarFontSize"
static let timelineFontSize = "timelineFontSize"
static let timelineSortDirection = "timelineSortDirection"
static let timelineGroupByFeed = "timelineGroupByFeed"
static let detailFontSize = "detailFontSize"
static let openInBrowserInBackground = "openInBrowserInBackground"
static let subscribeToFeedsInDefaultBrowser = "subscribeToFeedsInDefaultBrowser"
static let articleTextSize = "articleTextSize"
static let refreshInterval = "refreshInterval"
static let addFeedAccountID = "addWebFeedAccountID"
static let addFeedFolderName = "addWebFeedFolderName"
static let addFolderAccountID = "addFolderAccountID"
static let importOPMLAccountID = "importOPMLAccountID"
static let exportOPMLAccountID = "exportOPMLAccountID"
static let defaultBrowserID = "defaultBrowserID"
static let currentThemeName = "currentThemeName"
static let articleContentJavascriptEnabled = "articleContentJavascriptEnabled"
// Hidden prefs
static let showDebugMenu = "ShowDebugMenu"
static let timelineShowsSeparators = "CorreiaSeparators"
static let showTitleOnMainWindow = "KafasisTitleMode"
static let feedDoubleClickMarkAsRead = "GruberFeedDoubleClickMarkAsRead"
static let suppressSyncOnLaunch = "DevroeSuppressSyncOnLaunch"
#if !MAC_APP_STORE
static let webInspectorEnabled = "WebInspectorEnabled"
static let webInspectorStartsAttached = "__WebInspectorPageGroupLevel1__.WebKit2InspectorStartsAttached"
#endif
}
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
}()
let isFirstRun: Bool = {
if let _ = UserDefaults.standard.object(forKey: Key.firstRunDate) as? Date {
return false
}
firstRunDate = Date()
return true
}()
var windowState: [AnyHashable : Any]? {
get {
return UserDefaults.standard.object(forKey: Key.windowState) as? [AnyHashable : Any]
}
set {
UserDefaults.standard.set(newValue, forKey: Key.windowState)
}
}
var lastImageCacheFlushDate: Date? {
get {
return AppDefaults.date(for: Key.lastImageCacheFlushDate)
}
set {
AppDefaults.setDate(for: Key.lastImageCacheFlushDate, newValue)
}
}
var openInBrowserInBackground: Bool {
get {
return AppDefaults.bool(for: Key.openInBrowserInBackground)
}
set {
AppDefaults.setBool(for: Key.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: Key.subscribeToFeedsInDefaultBrowser)
}
set {
subscribeToFeedDefaults.set(newValue, forKey: Key.subscribeToFeedsInDefaultBrowser)
}
}
var sidebarFontSize: FontSize {
get {
return fontSize(for: Key.sidebarFontSize)
}
set {
AppDefaults.setFontSize(for: Key.sidebarFontSize, newValue)
}
}
var timelineFontSize: FontSize {
get {
return fontSize(for: Key.timelineFontSize)
}
set {
AppDefaults.setFontSize(for: Key.timelineFontSize, newValue)
}
}
var detailFontSize: FontSize {
get {
return fontSize(for: Key.detailFontSize)
}
set {
AppDefaults.setFontSize(for: Key.detailFontSize, newValue)
}
}
var addFeedAccountID: String? {
get {
return AppDefaults.string(for: Key.addFeedAccountID)
}
set {
AppDefaults.setString(for: Key.addFeedAccountID, newValue)
}
}
var addFeedFolderName: String? {
get {
return AppDefaults.string(for: Key.addFeedFolderName)
}
set {
AppDefaults.setString(for: Key.addFeedFolderName, newValue)
}
}
var addFolderAccountID: String? {
get {
return AppDefaults.string(for: Key.addFolderAccountID)
}
set {
AppDefaults.setString(for: Key.addFolderAccountID, newValue)
}
}
var importOPMLAccountID: String? {
get {
return AppDefaults.string(for: Key.importOPMLAccountID)
}
set {
AppDefaults.setString(for: Key.importOPMLAccountID, newValue)
}
}
var exportOPMLAccountID: String? {
get {
return AppDefaults.string(for: Key.exportOPMLAccountID)
}
set {
AppDefaults.setString(for: Key.exportOPMLAccountID, newValue)
}
}
var defaultBrowserID: String? {
get {
return AppDefaults.string(for: Key.defaultBrowserID)
}
set {
AppDefaults.setString(for: Key.defaultBrowserID, newValue)
}
}
var currentThemeName: String? {
get {
return AppDefaults.string(for: Key.currentThemeName)
}
set {
AppDefaults.setString(for: Key.currentThemeName, newValue)
}
}
var showTitleOnMainWindow: Bool {
return AppDefaults.bool(for: Key.showTitleOnMainWindow)
}
var showDebugMenu: Bool {
return AppDefaults.bool(for: Key.showDebugMenu)
}
var feedDoubleClickMarkAsRead: Bool {
get {
return AppDefaults.bool(for: Key.feedDoubleClickMarkAsRead)
}
set {
AppDefaults.setBool(for: Key.feedDoubleClickMarkAsRead, newValue)
}
}
var suppressSyncOnLaunch: Bool {
get {
return AppDefaults.bool(for: Key.suppressSyncOnLaunch)
}
set {
AppDefaults.setBool(for: Key.suppressSyncOnLaunch, newValue)
}
}
#if !MAC_APP_STORE
var webInspectorEnabled: Bool {
get {
return AppDefaults.bool(for: Key.webInspectorEnabled)
}
set {
AppDefaults.setBool(for: Key.webInspectorEnabled, newValue)
}
}
var webInspectorStartsAttached: Bool {
get {
return AppDefaults.bool(for: Key.webInspectorStartsAttached)
}
set {
AppDefaults.setBool(for: Key.webInspectorStartsAttached, newValue)
}
}
#endif
var timelineSortDirection: ComparisonResult {
get {
return AppDefaults.sortDirection(for: Key.timelineSortDirection)
}
set {
AppDefaults.setSortDirection(for: Key.timelineSortDirection, newValue)
}
}
var timelineGroupByFeed: Bool {
get {
return AppDefaults.bool(for: Key.timelineGroupByFeed)
}
set {
AppDefaults.setBool(for: Key.timelineGroupByFeed, newValue)
}
}
var timelineShowsSeparators: Bool {
return AppDefaults.bool(for: Key.timelineShowsSeparators)
}
var articleTextSize: ArticleTextSize {
get {
let rawValue = UserDefaults.standard.integer(forKey: Key.articleTextSize)
return ArticleTextSize(rawValue: rawValue) ?? ArticleTextSize.large
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: Key.articleTextSize)
}
}
var refreshInterval: RefreshInterval {
get {
let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval)
return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: Key.refreshInterval)
}
}
var isArticleContentJavascriptEnabled: Bool {
get {
UserDefaults.standard.bool(forKey: Key.articleContentJavascriptEnabled)
}
set {
UserDefaults.standard.set(newValue, forKey: Key.articleContentJavascriptEnabled)
}
}
func registerDefaults() {
#if DEBUG
let showDebugMenu = true
#else
let showDebugMenu = false
#endif
let defaults: [String : Any] = [
Key.sidebarFontSize: FontSize.medium.rawValue,
Key.timelineFontSize: FontSize.medium.rawValue,
Key.detailFontSize: FontSize.medium.rawValue,
Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue,
Key.timelineGroupByFeed: false,
"NSScrollViewShouldScrollUnderTitlebar": false,
Key.refreshInterval: RefreshInterval.everyHour.rawValue,
Key.showDebugMenu: showDebugMenu,
Key.currentThemeName: Self.defaultThemeName,
Key.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: Key.firstRunDate)
}
set {
AppDefaults.setDate(for: Key.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)
}
}
}