From 12a0d13971022f5121d7e28865787eb0c4013a03 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 30 Jun 2020 22:31:12 +0800 Subject: [PATCH 1/7] Changes viewModel to @StateObject --- Multiplatform/iOS/Settings/SettingsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Multiplatform/iOS/Settings/SettingsView.swift b/Multiplatform/iOS/Settings/SettingsView.swift index a20e8c791..baa7278dc 100644 --- a/Multiplatform/iOS/Settings/SettingsView.swift +++ b/Multiplatform/iOS/Settings/SettingsView.swift @@ -56,7 +56,7 @@ struct SettingsView: View { let sortedAccounts = AccountManager.shared.sortedAccounts @Environment(\.presentationMode) var presentationMode - @ObservedObject private var viewModel = SettingsViewModel() + @StateObject private var viewModel = SettingsViewModel() var body: some View { NavigationView { From 0e0f46fa496aaf8981fae80138bff2820c3e7280 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 1 Jul 2020 21:06:40 +0800 Subject: [PATCH 2/7] Refactors `AppDefaults` to `AppSettings` This commit makes some assumptions: - `AppSettings` is an `ObservableObject` that uses `@AppStorage` where possible, which sets default values. - Each change to an property triggers an `objectWillChange.send()` call. - `IconSize` is not used. Instead, it defaults to 40.0 with minimums and maximums of 20.0 and 60.0, controlled via Timeline settings. --- Multiplatform/Shared/AppDefaults.swift | 16 -- Multiplatform/Shared/AppSettings.swift | 180 ++++++++++++++++++ .../Shared/SceneNavigationView.swift | 2 + Multiplatform/iOS/AppSettings.swift | 72 +++++++ Multiplatform/iOS/Settings/SettingsView.swift | 9 +- .../Submenus/TimelineLayoutView.swift | 82 ++++++++ NetNewsWire.xcodeproj/project.pbxproj | 40 ++-- 7 files changed, 370 insertions(+), 31 deletions(-) create mode 100644 Multiplatform/Shared/AppSettings.swift create mode 100644 Multiplatform/iOS/AppSettings.swift create mode 100644 Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift diff --git a/Multiplatform/Shared/AppDefaults.swift b/Multiplatform/Shared/AppDefaults.swift index 5dc9f1910..265a16028 100644 --- a/Multiplatform/Shared/AppDefaults.swift +++ b/Multiplatform/Shared/AppDefaults.swift @@ -8,23 +8,7 @@ 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") - } - } - -} struct AppDefaults { diff --git a/Multiplatform/Shared/AppSettings.swift b/Multiplatform/Shared/AppSettings.swift new file mode 100644 index 000000000..68d4a76e8 --- /dev/null +++ b/Multiplatform/Shared/AppSettings.swift @@ -0,0 +1,180 @@ +// +// AppSettings.swift +// NetNewsWire +// +// Created by Stuart Breckenridge on 1/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import SwiftUI + +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 AppSettings: ObservableObject { + + #if os(macOS) + static let store: UserDefaults = UserDefaults.standard + #endif + + #if os(iOS) + static let store: UserDefaults = { + let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String + let suiteName = "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)" + return UserDefaults.init(suiteName: suiteName)! + }() + #endif + + public static let shared = AppSettings() + private init() {} + + struct Key { + static let refreshInterval = "refreshInterval" + static let hideDockUnreadCount = "JustinMillerHideDockUnreadCount" + static let userInterfaceColorPalette = "userInterfaceColorPalette" + static let activeExtensionPointIDs = "activeExtensionPointIDs" + static let lastImageCacheFlushDate = "lastImageCacheFlushDate" + static let firstRunDate = "firstRunDate" + static let timelineGroupByFeed = "timelineGroupByFeed" + static let refreshClearsReadArticles = "refreshClearsReadArticles" + static let timelineNumberOfLines = "timelineNumberOfLines" + static let timelineIconSize = "timelineIconSize" + static let timelineSortDirection = "timelineSortDirection" + static let articleFullscreenAvailable = "articleFullscreenAvailable" + static let articleFullscreenEnabled = "articleFullscreenEnabled" + static let confirmMarkAllAsRead = "confirmMarkAllAsRead" + static let lastRefresh = "lastRefresh" + static let addWebFeedAccountID = "addWebFeedAccountID" + static let addWebFeedFolderName = "addWebFeedFolderName" + static let addFolderAccountID = "addFolderAccountID" + } + + // MARK: Development Builds + let isDeveloperBuild: Bool = { + if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" { + return true + } + return false + }() + + // MARK: First Run Details + func isFirstRun() -> Bool { + if let _ = AppSettings.store.object(forKey: Key.firstRunDate) as? Date { + return false + } + firstRunDate = Date() + return true + } + + var firstRunDate: Date? { + set { + AppSettings.store.setValue(newValue, forKey: Key.firstRunDate) + objectWillChange.send() + } + get { + AppSettings.store.object(forKey: Key.firstRunDate) as? Date + } + } + + // MARK: Refresh Timings + @AppStorage(wrappedValue: RefreshInterval.everyHour, Key.refreshInterval, store: store) var refreshInterval: RefreshInterval + + // MARK: Dock Badge + @AppStorage(wrappedValue: false, Key.hideDockUnreadCount, store: store) var hideDockUnreadCount + + // MARK: Color Palette + var userInterfaceColorPalette: UserInterfaceColorPalette { + get { + if let palette = UserInterfaceColorPalette(rawValue: AppSettings.store.integer(forKey: Key.userInterfaceColorPalette)) { + return palette + } + return .automatic + } + set { + AppSettings.store.set(newValue.rawValue, forKey: Key.userInterfaceColorPalette) + objectWillChange.send() + } + } + + // MARK: Feeds & Folders + @AppStorage(Key.addWebFeedAccountID, store: store) var addWebFeedAccountID: String? + + @AppStorage(Key.addWebFeedFolderName, store: store) var addWebFeedFolderName: String? + + @AppStorage(Key.addFolderAccountID, store: store) var addFolderAccountID: String? + + @AppStorage(wrappedValue: false, Key.confirmMarkAllAsRead, store: store) var confirmMarkAllAsRead: Bool + + // MARK: Extension Points + var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { + get { + return AppSettings.store.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] + } + set { + UserDefaults.standard.set(newValue, forKey: Key.activeExtensionPointIDs) + objectWillChange.send() + } + } + + // MARK: Image Cache + var lastImageCacheFlushDate: Date? { + set { + AppSettings.store.setValue(newValue, forKey: Key.lastImageCacheFlushDate) + objectWillChange.send() + } + get { + AppSettings.store.object(forKey: Key.lastImageCacheFlushDate) as? Date + } + } + + // MARK: Timeline + @AppStorage(wrappedValue: false, Key.timelineGroupByFeed, store: store) var timelineGroupByFeed: Bool + + @AppStorage(wrappedValue: 3, Key.timelineNumberOfLines, store: store) var timelineNumberOfLines: Int { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: 40.0, Key.timelineIconSize, store: store) var timelineIconSize: Double { + didSet { + objectWillChange.send() + } + } + + /// Set to `true` to sort oldest to newest, `false` for newest to oldest. Default is `false`. + @AppStorage(wrappedValue: false, Key.timelineSortDirection, store: store) var timelineSortDirection: Bool + + // MARK: Refresh + @AppStorage(wrappedValue: false, Key.refreshClearsReadArticles, store: store) var refreshClearsReadArticles: Bool + + // MARK: Articles + @AppStorage(wrappedValue: false, Key.articleFullscreenAvailable, store: store) var articleFullscreenAvailable: Bool + + // MARK: Refresh + var lastRefresh: Date? { + set { + AppSettings.store.setValue(newValue, forKey: Key.lastRefresh) + objectWillChange.send() + } + get { + AppSettings.store.object(forKey: Key.lastRefresh) as? Date + } + } + +} diff --git a/Multiplatform/Shared/SceneNavigationView.swift b/Multiplatform/Shared/SceneNavigationView.swift index 533c6692a..fbc2b80c9 100644 --- a/Multiplatform/Shared/SceneNavigationView.swift +++ b/Multiplatform/Shared/SceneNavigationView.swift @@ -22,6 +22,7 @@ struct SceneNavigationView: View { #else if horizontalSizeClass == .compact { CompactSidebarContainerView() + } else { RegularSidebarContainerView() } @@ -46,6 +47,7 @@ struct SceneNavigationView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) #endif } + } } diff --git a/Multiplatform/iOS/AppSettings.swift b/Multiplatform/iOS/AppSettings.swift new file mode 100644 index 000000000..80caba537 --- /dev/null +++ b/Multiplatform/iOS/AppSettings.swift @@ -0,0 +1,72 @@ +// +// AppSettings.swift +// Multiplatform iOS +// +// Created by Stuart Breckenridge on 1/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation + +enum ColorPalette: 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") + } + } +} + + +class AppSettings: ObservableObject { + + struct Key { + static let userInterfaceColorPalette = "userInterfaceColorPalette" + static let activeExtensionPointIDs = "activeExtensionPointIDs" + static let lastImageCacheFlushDate = "lastImageCacheFlushDate" + static let firstRunDate = "firstRunDate" + static let timelineGroupByFeed = "timelineGroupByFeed" + static let refreshClearsReadArticles = "refreshClearsReadArticles" + static let timelineNumberOfLines = "timelineNumberOfLines" + static let timelineIconSize = "timelineIconSize" + static let timelineSortDirection = "timelineSortDirection" + static let articleFullscreenAvailable = "articleFullscreenAvailable" + static let articleFullscreenEnabled = "articleFullscreenEnabled" + static let confirmMarkAllAsRead = "confirmMarkAllAsRead" + static let lastRefresh = "lastRefresh" + static let addWebFeedAccountID = "addWebFeedAccountID" + static let addWebFeedFolderName = "addWebFeedFolderName" + static let addFolderAccountID = "addFolderAccountID" + } + + 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 let _ = AppDefaults.shared.object(forKey: Key.firstRunDate) as? Date { + return false + } + firstRunDate = Date() + return true + }() + + + + + + + + +} diff --git a/Multiplatform/iOS/Settings/SettingsView.swift b/Multiplatform/iOS/Settings/SettingsView.swift index baa7278dc..61cfb879f 100644 --- a/Multiplatform/iOS/Settings/SettingsView.swift +++ b/Multiplatform/iOS/Settings/SettingsView.swift @@ -57,6 +57,7 @@ struct SettingsView: View { @Environment(\.presentationMode) var presentationMode @StateObject private var viewModel = SettingsViewModel() + @StateObject private var settings = AppSettings.shared var body: some View { NavigationView { @@ -128,11 +129,11 @@ struct SettingsView: View { var timeline: some View { Section(header: Text("Timeline"), content: { - Toggle("Sort Oldest to Newest", isOn: .constant(true)) - Toggle("Group by Feed", isOn: .constant(true)) - Toggle("Refresh to Clear Read Articles", isOn: .constant(true)) + Toggle("Sort Oldest to Newest", isOn: $settings.timelineSortDirection) + Toggle("Group by Feed", isOn: $settings.timelineGroupByFeed) + Toggle("Refresh to Clear Read Articles", isOn: $settings.refreshClearsReadArticles) NavigationLink( - destination: EmptyView(), + destination: TimelineLayoutView().environmentObject(settings), label: { Text("Timeline Layout") }) diff --git a/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift b/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift new file mode 100644 index 000000000..ce12a95a0 --- /dev/null +++ b/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift @@ -0,0 +1,82 @@ +// +// TimelineLayoutView.swift +// Multiplatform iOS +// +// Created by Stuart Breckenridge on 1/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI + +struct TimelineLayoutView: View { + + @EnvironmentObject private var appSettings: AppSettings + + private let sampleTitle = "Lorem dolor sed viverra ipsum. Gravida rutrum quisque non tellus. Rutrum tellus pellentesque eu tincidunt tortor. Sed blandit libero volutpat sed cras ornare. Et netus et malesuada fames ac. Ultrices eros in cursus turpis massa tincidunt dui ut ornare. Lacus sed viverra tellus in. Sollicitudin ac orci phasellus egestas. Purus in mollis nunc sed. Sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque. Interdum consectetur libero id faucibus nisl tincidunt eget." + + var body: some View { + VStack(spacing: 0) { + List { + Section(header: Text("Icon Size"), content: { + iconSize + }) + Section(header: Text("Number of Lines"), content: { + numberOfLines + }) } + .listStyle(InsetGroupedListStyle()) + + Divider() + timelineRowPreview.padding() + Divider() + } + .navigationBarTitle("Timeline Layout") + } + + var iconSize: some View { + Slider(value: $appSettings.timelineIconSize, in: 20...60, minimumValueLabel: Text("Small"), maximumValueLabel: Text("Large"), label: { + Text(String(appSettings.timelineIconSize)) + }) + } + + var numberOfLines: some View { + Stepper(value: $appSettings.timelineNumberOfLines, in: 1...5, label: { + Text("Title") + }) + } + + var timelineRowPreview: some View { + + HStack(alignment: .top) { + Image(systemName: "circle.fill") + .resizable() + .frame(width: 10, height: 10, alignment: .top) + .foregroundColor(.accentColor) + + Image(systemName: "paperplane.circle") + .resizable() + .frame(width: CGFloat(appSettings.timelineIconSize), height: CGFloat(appSettings.timelineIconSize), alignment: .top) + .foregroundColor(.accentColor) + + VStack(alignment: .leading, spacing: 4) { + Text(sampleTitle) + .font(.headline) + .lineLimit(appSettings.timelineNumberOfLines) + HStack { + Text("Feed Name") + .foregroundColor(.secondary) + .font(.footnote) + Spacer() + Text("10:31") + .font(.footnote) + .foregroundColor(.secondary) + } + } + } + } +} + +struct TimelineLayout_Previews: PreviewProvider { + static var previews: some View { + TimelineLayoutView() + } +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index b71450964..6df521f12 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -18,8 +18,11 @@ 1729529724AA1CD000D65E66 /* MacPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529624AA1CD000D65E66 /* MacPreferencesView.swift */; }; 1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529A24AA1FD200D65E66 /* MacSearchField.swift */; }; 172952B024AA287100D65E66 /* CompactSidebarContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172952AF24AA287100D65E66 /* CompactSidebarContainerView.swift */; }; + 1776E88E24AC5F8A00E78166 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppSettings.swift */; }; + 1776E88F24AC5F8A00E78166 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppSettings.swift */; }; 179DB1DFBCF9177104B12E0F /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; 179DB3CE822BFCC2D774D9F4 /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; + 17B223DC24AC24D2001E4592 /* TimelineLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */; }; 3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */; }; 3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */; }; 3B826DCC2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; }; @@ -1701,7 +1704,9 @@ 1729529624AA1CD000D65E66 /* MacPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacPreferencesView.swift; sourceTree = ""; }; 1729529A24AA1FD200D65E66 /* MacSearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacSearchField.swift; sourceTree = ""; }; 172952AF24AA287100D65E66 /* CompactSidebarContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactSidebarContainerView.swift; sourceTree = ""; }; + 1776E88D24AC5F8A00E78166 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = ""; }; + 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLayoutView.swift; sourceTree = ""; }; 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAccountViewController.swift; sourceTree = ""; }; 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = ""; }; 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsFeedWranglerWindowController.swift; sourceTree = ""; }; @@ -2346,6 +2351,7 @@ isa = PBXGroup; children = ( 172199C824AB228900A31D04 /* SettingsView.swift */, + 17B223B924AC24A8001E4592 /* Submenus */, ); path = Settings; sourceTree = ""; @@ -2378,6 +2384,14 @@ path = Views; sourceTree = ""; }; + 17B223B924AC24A8001E4592 /* Submenus */ = { + isa = PBXGroup; + children = ( + 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */, + ); + path = Submenus; + sourceTree = ""; + }; 510289CE2451BA1E00426DDF /* Twitter */ = { isa = PBXGroup; children = ( @@ -2690,6 +2704,7 @@ children = ( 51E4992524A80AAB00B667CB /* AppAssets.swift */, 51E4992824A866F000B667CB /* AppDefaults.swift */, + 1776E88D24AC5F8A00E78166 /* AppSettings.swift */, 51E4995824A873F900B667CB /* ErrorHandler.swift */, 51C0513624A77DF700194D5E /* MainApp.swift */, 51E499D724A912C200B667CB /* SceneModel.swift */, @@ -3889,46 +3904,46 @@ TargetAttributes = { 51314636235A7BBE00387FDC = { CreatedOnToolsVersion = 11.2; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; LastSwiftMigration = 1120; ProvisioningStyle = Automatic; }; 513C5CE5232571C2003D4054 = { CreatedOnToolsVersion = 11.0; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 518B2ED12351B3DD00400001 = { CreatedOnToolsVersion = 11.2; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; TestTargetID = 840D617B2029031C009BC708; }; 51C0513C24A77DF800194D5E = { CreatedOnToolsVersion = 12.0; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 51C0514324A77DF800194D5E = { CreatedOnToolsVersion = 12.0; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 6581C73220CED60000F4AD34 = { - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 65ED3FA2235DEF6C0081F399 = { - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 65ED4090235DEF770081F399 = { - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -3938,7 +3953,7 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.HardenedRuntime = { @@ -3948,7 +3963,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -4772,6 +4787,7 @@ 51E4991E24A8094300B667CB /* RSImage-AppIcons.swift in Sources */, 51E499D824A912C200B667CB /* SceneModel.swift in Sources */, 51919FB324AAB97900541E64 /* FeedImageLoader.swift in Sources */, + 17B223DC24AC24D2001E4592 /* TimelineLayoutView.swift in Sources */, 51E4991324A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */, 51E4993C24A8709900B667CB /* AppDelegate.swift in Sources */, 51E498F924A8085D00B667CB /* SmartFeed.swift in Sources */, @@ -4791,6 +4807,7 @@ 51E4991524A808FF00B667CB /* ArticleStringFormatter.swift in Sources */, 51919FEE24AB85E400541E64 /* TimelineContainerView.swift in Sources */, 51E4995724A8734D00B667CB /* ExtensionPoint.swift in Sources */, + 1776E88E24AC5F8A00E78166 /* AppSettings.swift in Sources */, 51E4991124A808DE00B667CB /* SmallIconProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4832,6 +4849,7 @@ 51E49A0124A91FC100B667CB /* RegularSidebarContainerView.swift in Sources */, 51E4995B24A875D500B667CB /* ArticlePasteboardWriter.swift in Sources */, 51E4993424A867E700B667CB /* UserInfoKey.swift in Sources */, + 1776E88F24AC5F8A00E78166 /* AppSettings.swift in Sources */, 1729529724AA1CD000D65E66 /* MacPreferencesView.swift in Sources */, 51E4994C24A8734C00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, 1729529324AA1CAA00D65E66 /* AccountsPreferencesView.swift in Sources */, From 4e8e7924718a14fae9dca636b1aa0a2e6e309e56 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Thu, 2 Jul 2020 06:59:33 +0800 Subject: [PATCH 3/7] Defaults added from NetNewsWire macOS --- Multiplatform/Shared/AppSettings.swift | 175 ++++++++++++++++++++++--- 1 file changed, 158 insertions(+), 17 deletions(-) diff --git a/Multiplatform/Shared/AppSettings.swift b/Multiplatform/Shared/AppSettings.swift index 68d4a76e8..a584034c2 100644 --- a/Multiplatform/Shared/AppSettings.swift +++ b/Multiplatform/Shared/AppSettings.swift @@ -26,6 +26,13 @@ enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable { } } +enum FontSize: Int { + case small = 0 + case medium = 1 + case large = 2 + case veryLarge = 3 +} + final class AppSettings: ObservableObject { #if os(macOS) @@ -44,26 +51,54 @@ final class AppSettings: ObservableObject { private init() {} struct Key { + + // Shared Defaults static let refreshInterval = "refreshInterval" static let hideDockUnreadCount = "JustinMillerHideDockUnreadCount" - static let userInterfaceColorPalette = "userInterfaceColorPalette" static let activeExtensionPointIDs = "activeExtensionPointIDs" static let lastImageCacheFlushDate = "lastImageCacheFlushDate" static let firstRunDate = "firstRunDate" - static let timelineGroupByFeed = "timelineGroupByFeed" - static let refreshClearsReadArticles = "refreshClearsReadArticles" - static let timelineNumberOfLines = "timelineNumberOfLines" - static let timelineIconSize = "timelineIconSize" - static let timelineSortDirection = "timelineSortDirection" - static let articleFullscreenAvailable = "articleFullscreenAvailable" - static let articleFullscreenEnabled = "articleFullscreenEnabled" - static let confirmMarkAllAsRead = "confirmMarkAllAsRead" static let lastRefresh = "lastRefresh" static let addWebFeedAccountID = "addWebFeedAccountID" static let addWebFeedFolderName = "addWebFeedFolderName" static let addFolderAccountID = "addFolderAccountID" + static let timelineSortDirection = "timelineSortDirection" + + // iOS Defaults + static let userInterfaceColorPalette = "userInterfaceColorPalette" + static let timelineGroupByFeed = "timelineGroupByFeed" + static let refreshClearsReadArticles = "refreshClearsReadArticles" + static let timelineNumberOfLines = "timelineNumberOfLines" + static let timelineIconSize = "timelineIconSize" + static let articleFullscreenAvailable = "articleFullscreenAvailable" + static let articleFullscreenEnabled = "articleFullscreenEnabled" + static let confirmMarkAllAsRead = "confirmMarkAllAsRead" + + // macOS Defaults + static let windowState = "windowState" + static let sidebarFontSize = "sidebarFontSize" + static let timelineFontSize = "timelineFontSize" + static let detailFontSize = "detailFontSize" + static let openInBrowserInBackground = "openInBrowserInBackground" + static let importOPMLAccountID = "importOPMLAccountID" + static let exportOPMLAccountID = "exportOPMLAccountID" + static let defaultBrowserID = "defaultBrowserID" + + // Hidden macOS Defaults + static let showDebugMenu = "ShowDebugMenu" + static let timelineShowsSeparators = "CorreiaSeparators" + static let showTitleOnMainWindow = "KafasisTitleMode" + + #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 + // MARK: Development Builds let isDeveloperBuild: Bool = { if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" { @@ -73,14 +108,6 @@ final class AppSettings: ObservableObject { }() // MARK: First Run Details - func isFirstRun() -> Bool { - if let _ = AppSettings.store.object(forKey: Key.firstRunDate) as? Date { - return false - } - firstRunDate = Date() - return true - } - var firstRunDate: Date? { set { AppSettings.store.setValue(newValue, forKey: Key.firstRunDate) @@ -177,4 +204,118 @@ final class AppSettings: ObservableObject { } } + // MARK: Window State + var windowState: [AnyHashable : Any]? { + get { + return AppSettings.store.object(forKey: Key.windowState) as? [AnyHashable : Any] + } + set { + UserDefaults.standard.set(newValue, forKey: Key.windowState) + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.openInBrowserInBackground, store: store) var openInBrowserInBackground: Bool { + didSet { + objectWillChange.send() + } + } + + var sidebarFontSize: FontSize { + get { + return fontSize(for: Key.sidebarFontSize) + } + set { + AppSettings.store.set(newValue.rawValue, forKey: Key.sidebarFontSize) + objectWillChange.send() + } + } + + var timelineFontSize: FontSize { + get { + return fontSize(for: Key.timelineFontSize) + } + set { + AppSettings.store.set(newValue.rawValue, forKey: Key.timelineFontSize) + objectWillChange.send() + } + } + + var detailFontSize: FontSize { + get { + return fontSize(for: Key.detailFontSize) + } + set { + AppSettings.store.set(newValue.rawValue, forKey: Key.detailFontSize) + objectWillChange.send() + } + } + + @AppStorage(Key.importOPMLAccountID, store: store) var importOPMLAccountID: String? { + didSet { + objectWillChange.send() + } + } + + @AppStorage(Key.exportOPMLAccountID, store: store) var exportOPMLAccountID: String? { + didSet { + objectWillChange.send() + } + } + + @AppStorage(Key.defaultBrowserID, store: store) var defaultBrowserID: String? { + didSet { + objectWillChange.send() + } + } + + @AppStorage(Key.showTitleOnMainWindow, store: store) var showTitleOnMainWindow: Bool? { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.showDebugMenu, store: store) var showDebugMenu: Bool { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.timelineShowsSeparators, store: store) var timelineShowsSeparators: Bool { + didSet { + objectWillChange.send() + } + } + + #if !MAC_APP_STORE + @AppStorage(wrappedValue: false, Key.webInspectorEnabled, store: store) var webInspectorEnabled: Bool { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.webInspectorStartsAttached, store: store) var webInspectorStartsAttached: Bool { + didSet { + objectWillChange.send() + } + } + #endif + + +} + +extension AppSettings { + + func isFirstRun() -> Bool { + if let _ = AppSettings.store.object(forKey: Key.firstRunDate) as? Date { + return false + } + firstRunDate = Date() + return true + } + + func fontSize(for key: String) -> FontSize { + // Punted till after 1.0. + return .medium + } } From 76e0d5e555c1ef1b2940b219d4f373efb9cb156e Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Thu, 2 Jul 2020 09:57:36 +0800 Subject: [PATCH 4/7] Convers AppDefaults to singleton --- Multiplatform/Shared/AppDefaults.swift | 450 ++++++++++-------- Multiplatform/Shared/AppSettings.swift | 321 ------------- Multiplatform/Shared/MainApp.swift | 4 +- Multiplatform/iOS/AppDelegate.swift | 8 +- Multiplatform/iOS/Settings/SettingsView.swift | 2 +- .../Submenus/TimelineLayoutView.swift | 4 +- Multiplatform/macOS/AppDelegate.swift | 6 +- .../{Views => }/MacPreferencesView.swift | 8 +- .../Preferences/Model/MacPreferences.swift | 96 ---- .../AccountsPreferencesView.swift | 0 .../AdvancedPreferencesView.swift | 2 +- .../View/GeneralPreferencesView.swift | 33 ++ .../Views/GeneralPreferencesView.swift | 33 -- NetNewsWire.xcodeproj/project.pbxproj | 46 +- .../ExtensionPointManager.swift | 6 +- .../AddWebFeedDefaultContainer.swift | 10 +- Shared/Extensions/CacheCleaner.swift | 6 +- Shared/Timer/AccountRefreshTimer.swift | 4 +- Shared/Timer/RefreshInterval.swift | 4 +- 19 files changed, 336 insertions(+), 707 deletions(-) delete mode 100644 Multiplatform/Shared/AppSettings.swift rename Multiplatform/macOS/Preferences/{Views => }/MacPreferencesView.swift (89%) delete mode 100644 Multiplatform/macOS/Preferences/Model/MacPreferences.swift rename Multiplatform/macOS/Preferences/{Views => View}/AccountsPreferencesView.swift (100%) rename Multiplatform/macOS/Preferences/{Views => View}/AdvancedPreferencesView.swift (96%) create mode 100644 Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift delete mode 100644 Multiplatform/macOS/Preferences/Views/GeneralPreferencesView.swift diff --git a/Multiplatform/Shared/AppDefaults.swift b/Multiplatform/Shared/AppDefaults.swift index 265a16028..1031c7ef0 100644 --- a/Multiplatform/Shared/AppDefaults.swift +++ b/Multiplatform/Shared/AppDefaults.swift @@ -2,290 +2,352 @@ // AppDefaults.swift // NetNewsWire // -// Created by Maurice Parker on 6/28/20. +// Created by Stuart Breckenridge on 1/7/20. // Copyright © 2020 Ranchero Software. All rights reserved. // import Foundation +import SwiftUI +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") + } + } +} -struct AppDefaults { +enum FontSize: Int { + case small = 0 + case medium = 1 + case large = 2 + case veryLarge = 3 +} +final class AppDefaults: ObservableObject { + #if os(macOS) - static var shared: UserDefaults = UserDefaults.standard + static let store: UserDefaults = UserDefaults.standard #endif #if os(iOS) - static var shared: UserDefaults = { + static let store: UserDefaults = { let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String let suiteName = "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)" return UserDefaults.init(suiteName: suiteName)! }() #endif + public static let shared = AppDefaults() + private init() {} + struct Key { + + // Shared Defaults static let refreshInterval = "refreshInterval" static let hideDockUnreadCount = "JustinMillerHideDockUnreadCount" - static let userInterfaceColorPalette = "userInterfaceColorPalette" static let activeExtensionPointIDs = "activeExtensionPointIDs" static let lastImageCacheFlushDate = "lastImageCacheFlushDate" static let firstRunDate = "firstRunDate" - static let timelineGroupByFeed = "timelineGroupByFeed" - static let refreshClearsReadArticles = "refreshClearsReadArticles" - static let timelineNumberOfLines = "timelineNumberOfLines" - static let timelineIconSize = "timelineIconSize" - static let timelineSortDirection = "timelineSortDirection" - static let articleFullscreenAvailable = "articleFullscreenAvailable" - static let articleFullscreenEnabled = "articleFullscreenEnabled" - static let confirmMarkAllAsRead = "confirmMarkAllAsRead" static let lastRefresh = "lastRefresh" static let addWebFeedAccountID = "addWebFeedAccountID" static let addWebFeedFolderName = "addWebFeedFolderName" static let addFolderAccountID = "addFolderAccountID" + static let timelineSortDirection = "timelineSortDirection" + + // iOS Defaults + static let userInterfaceColorPalette = "userInterfaceColorPalette" + static let timelineGroupByFeed = "timelineGroupByFeed" + static let refreshClearsReadArticles = "refreshClearsReadArticles" + static let timelineNumberOfLines = "timelineNumberOfLines" + static let timelineIconSize = "timelineIconSize" + static let articleFullscreenAvailable = "articleFullscreenAvailable" + static let articleFullscreenEnabled = "articleFullscreenEnabled" + static let confirmMarkAllAsRead = "confirmMarkAllAsRead" + + // macOS Defaults + static let windowState = "windowState" + static let sidebarFontSize = "sidebarFontSize" + static let timelineFontSize = "timelineFontSize" + static let detailFontSize = "detailFontSize" + static let openInBrowserInBackground = "openInBrowserInBackground" + static let importOPMLAccountID = "importOPMLAccountID" + static let exportOPMLAccountID = "exportOPMLAccountID" + static let defaultBrowserID = "defaultBrowserID" + static let checkForUpdatesAutomatically = "checkForUpdatesAutomatically" + static let downloadTestBuilds = "downloadTestBuild" + static let sendCrashLogs = "sendCrashLogs" + + // Hidden macOS Defaults + static let showDebugMenu = "ShowDebugMenu" + static let timelineShowsSeparators = "CorreiaSeparators" + static let showTitleOnMainWindow = "KafasisTitleMode" + + #if !MAC_APP_STORE + static let webInspectorEnabled = "WebInspectorEnabled" + static let webInspectorStartsAttached = "__WebInspectorPageGroupLevel1__.WebKit2InspectorStartsAttached" + #endif + } - - static let isDeveloperBuild: Bool = { + + private static let smallestFontSizeRawValue = FontSize.small.rawValue + private static let largestFontSizeRawValue = FontSize.veryLarge.rawValue + + // MARK: Development Builds + let isDeveloperBuild: Bool = { if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" { return true } return false }() - - static let isFirstRun: Bool = { - if let _ = AppDefaults.shared.object(forKey: Key.firstRunDate) as? Date { - return false - } - firstRunDate = Date() - return true - }() - static var refreshInterval: RefreshInterval { - get { - let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval) - return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour - } + // MARK: First Run Details + var firstRunDate: Date? { set { - UserDefaults.standard.set(newValue.rawValue, forKey: Key.refreshInterval) + AppDefaults.store.setValue(newValue, forKey: Key.firstRunDate) + objectWillChange.send() + } + get { + AppDefaults.store.object(forKey: Key.firstRunDate) as? Date } } - - static var hideDockUnreadCount: Bool { - return bool(for: Key.hideDockUnreadCount) + + // MARK: Refresh Interval + @AppStorage(wrappedValue: 4, Key.refreshInterval, store: store) var interval: Int { + didSet { + objectWillChange.send() + } } - - static var userInterfaceColorPalette: UserInterfaceColorPalette { + + var refreshInterval: RefreshInterval { + RefreshInterval(rawValue: interval) ?? RefreshInterval.everyHour + } + + // MARK: Dock Badge + @AppStorage(wrappedValue: false, Key.hideDockUnreadCount, store: store) var hideDockUnreadCount { + didSet { + objectWillChange.send() + } + } + + // MARK: Color Palette + var userInterfaceColorPalette: UserInterfaceColorPalette { get { - if let result = UserInterfaceColorPalette(rawValue: int(for: Key.userInterfaceColorPalette)) { - return result + if let palette = UserInterfaceColorPalette(rawValue: AppDefaults.store.integer(forKey: Key.userInterfaceColorPalette)) { + return palette } return .automatic } set { - setInt(for: Key.userInterfaceColorPalette, newValue.rawValue) - } - } - - static var addWebFeedAccountID: String? { - get { - return string(for: Key.addWebFeedAccountID) - } - set { - setString(for: Key.addWebFeedAccountID, newValue) + AppDefaults.store.set(newValue.rawValue, forKey: Key.userInterfaceColorPalette) + objectWillChange.send() } } - static var addWebFeedFolderName: String? { - get { - return string(for: Key.addWebFeedFolderName) - } - set { - setString(for: Key.addWebFeedFolderName, newValue) - } - } + // MARK: Feeds & Folders + @AppStorage(Key.addWebFeedAccountID, store: store) var addWebFeedAccountID: String? - static var addFolderAccountID: String? { - get { - return string(for: Key.addFolderAccountID) - } - set { - setString(for: Key.addFolderAccountID, newValue) - } - } + @AppStorage(Key.addWebFeedFolderName, store: store) var addWebFeedFolderName: String? - static var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { + @AppStorage(Key.addFolderAccountID, store: store) var addFolderAccountID: String? + + @AppStorage(wrappedValue: false, Key.confirmMarkAllAsRead, store: store) var confirmMarkAllAsRead: Bool + + // MARK: Extension Points + var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { get { - return UserDefaults.standard.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] + return AppDefaults.store.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] } set { UserDefaults.standard.set(newValue, forKey: Key.activeExtensionPointIDs) + objectWillChange.send() } } - static var lastImageCacheFlushDate: Date? { - get { - return date(for: Key.lastImageCacheFlushDate) - } + // MARK: Image Cache + var lastImageCacheFlushDate: Date? { set { - setDate(for: Key.lastImageCacheFlushDate, newValue) + AppDefaults.store.setValue(newValue, forKey: Key.lastImageCacheFlushDate) + objectWillChange.send() } - } - - static var timelineGroupByFeed: Bool { get { - return bool(for: Key.timelineGroupByFeed) - } - set { - setBool(for: Key.timelineGroupByFeed, newValue) - } - } - - static var refreshClearsReadArticles: Bool { - get { - return bool(for: Key.refreshClearsReadArticles) - } - set { - setBool(for: Key.refreshClearsReadArticles, newValue) - } - } - - static var timelineSortDirection: ComparisonResult { - get { - return sortDirection(for: Key.timelineSortDirection) - } - set { - setSortDirection(for: Key.timelineSortDirection, newValue) - } - } - - static var articleFullscreenAvailable: Bool { - get { - return bool(for: Key.articleFullscreenAvailable) - } - set { - setBool(for: Key.articleFullscreenAvailable, newValue) - } - } - - static var articleFullscreenEnabled: Bool { - get { - return bool(for: Key.articleFullscreenEnabled) - } - set { - setBool(for: Key.articleFullscreenEnabled, newValue) - } - } - - static var confirmMarkAllAsRead: Bool { - get { - return bool(for: Key.confirmMarkAllAsRead) - } - set { - setBool(for: Key.confirmMarkAllAsRead, newValue) + AppDefaults.store.object(forKey: Key.lastImageCacheFlushDate) as? Date } } - static var lastRefresh: Date? { + // MARK: Timeline + @AppStorage(wrappedValue: false, Key.timelineGroupByFeed, store: store) var timelineGroupByFeed: Bool + + @AppStorage(wrappedValue: 3, Key.timelineNumberOfLines, store: store) var timelineNumberOfLines: Int { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: 40.0, Key.timelineIconSize, store: store) var timelineIconSize: Double { + didSet { + objectWillChange.send() + } + } + + /// Set to `true` to sort oldest to newest, `false` for newest to oldest. Default is `false`. + @AppStorage(wrappedValue: false, Key.timelineSortDirection, store: store) var timelineSortDirection: Bool + + // MARK: Refresh + @AppStorage(wrappedValue: false, Key.refreshClearsReadArticles, store: store) var refreshClearsReadArticles: Bool + + // MARK: Articles + @AppStorage(wrappedValue: false, Key.articleFullscreenAvailable, store: store) var articleFullscreenAvailable: Bool + + // MARK: Refresh + var lastRefresh: Date? { + set { + AppDefaults.store.setValue(newValue, forKey: Key.lastRefresh) + objectWillChange.send() + } get { - return date(for: Key.lastRefresh) + AppDefaults.store.object(forKey: Key.lastRefresh) as? Date + } + } + + // MARK: Window State + var windowState: [AnyHashable : Any]? { + get { + return AppDefaults.store.object(forKey: Key.windowState) as? [AnyHashable : Any] } set { - setDate(for: Key.lastRefresh, newValue) + UserDefaults.standard.set(newValue, forKey: Key.windowState) + objectWillChange.send() } } - static var timelineNumberOfLines: Int { + @AppStorage(wrappedValue: false, Key.openInBrowserInBackground, store: store) var openInBrowserInBackground: Bool { + didSet { + objectWillChange.send() + } + } + + var sidebarFontSize: FontSize { get { - return int(for: Key.timelineNumberOfLines) + return fontSize(for: Key.sidebarFontSize) } set { - setInt(for: Key.timelineNumberOfLines, newValue) + AppDefaults.store.set(newValue.rawValue, forKey: Key.sidebarFontSize) + objectWillChange.send() } } - static var timelineIconSize: IconSize { + var timelineFontSize: FontSize { get { - let rawValue = AppDefaults.shared.integer(forKey: Key.timelineIconSize) - return IconSize(rawValue: rawValue) ?? IconSize.medium + return fontSize(for: Key.timelineFontSize) } set { - AppDefaults.shared.set(newValue.rawValue, forKey: Key.timelineIconSize) + AppDefaults.store.set(newValue.rawValue, forKey: Key.timelineFontSize) + objectWillChange.send() } } - static func registerDefaults() { - let defaults: [String : Any] = [Key.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue, - Key.timelineGroupByFeed: false, - Key.refreshClearsReadArticles: false, - Key.timelineNumberOfLines: 2, - Key.timelineIconSize: IconSize.medium.rawValue, - Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, - Key.articleFullscreenAvailable: false, - Key.articleFullscreenEnabled: false, - Key.confirmMarkAllAsRead: true] - AppDefaults.shared.register(defaults: defaults) - } - -} - -private extension AppDefaults { - - static var firstRunDate: Date? { + var detailFontSize: FontSize { get { - return date(for: Key.firstRunDate) + return fontSize(for: Key.detailFontSize) } set { - setDate(for: Key.firstRunDate, newValue) + AppDefaults.store.set(newValue.rawValue, forKey: Key.detailFontSize) + objectWillChange.send() } } - - static func string(for key: String) -> String? { - return AppDefaults.shared.string(forKey: key) - } - static func setString(for key: String, _ value: String?) { - AppDefaults.shared.set(value, forKey: key) - } - - static func bool(for key: String) -> Bool { - return AppDefaults.shared.bool(forKey: key) - } - - static func setBool(for key: String, _ flag: Bool) { - AppDefaults.shared.set(flag, forKey: key) - } - - static func int(for key: String) -> Int { - return AppDefaults.shared.integer(forKey: key) - } - - static func setInt(for key: String, _ x: Int) { - AppDefaults.shared.set(x, forKey: key) - } - - static func date(for key: String) -> Date? { - return AppDefaults.shared.object(forKey: key) as? Date - } - - static func setDate(for key: String, _ date: Date?) { - AppDefaults.shared.set(date, forKey: key) - } - - static func sortDirection(for key:String) -> ComparisonResult { - let rawInt = int(for: key) - if rawInt == ComparisonResult.orderedAscending.rawValue { - return .orderedAscending + @AppStorage(Key.importOPMLAccountID, store: store) var importOPMLAccountID: String? { + didSet { + objectWillChange.send() } - return .orderedDescending } - - static func setSortDirection(for key: String, _ value: ComparisonResult) { - if value == .orderedAscending { - setInt(for: key, ComparisonResult.orderedAscending.rawValue) + + @AppStorage(Key.exportOPMLAccountID, store: store) var exportOPMLAccountID: String? { + didSet { + objectWillChange.send() } - else { - setInt(for: key, ComparisonResult.orderedDescending.rawValue) + } + + @AppStorage(Key.defaultBrowserID, store: store) var defaultBrowserID: String? { + didSet { + objectWillChange.send() + } + } + + @AppStorage(Key.showTitleOnMainWindow, store: store) var showTitleOnMainWindow: Bool? { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.showDebugMenu, store: store) var showDebugMenu: Bool { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.timelineShowsSeparators, store: store) var timelineShowsSeparators: Bool { + didSet { + objectWillChange.send() + } + } + + #if !MAC_APP_STORE + @AppStorage(wrappedValue: false, Key.webInspectorEnabled, store: store) var webInspectorEnabled: Bool { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.webInspectorStartsAttached, store: store) var webInspectorStartsAttached: Bool { + didSet { + objectWillChange.send() + } + } + #endif + + @AppStorage(wrappedValue: true, Key.checkForUpdatesAutomatically, store: store) var checkForUpdatesAutomatically: Bool { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.downloadTestBuilds, store: store) var downloadTestBuilds: Bool { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: true, Key.sendCrashLogs, store: store) var sendCrashLogs: Bool { + didSet { + objectWillChange.send() } } } + +extension AppDefaults { + + func isFirstRun() -> Bool { + if let _ = AppDefaults.store.object(forKey: Key.firstRunDate) as? Date { + return false + } + firstRunDate = Date() + return true + } + + func fontSize(for key: String) -> FontSize { + // Punted till after 1.0. + return .medium + } +} diff --git a/Multiplatform/Shared/AppSettings.swift b/Multiplatform/Shared/AppSettings.swift deleted file mode 100644 index a584034c2..000000000 --- a/Multiplatform/Shared/AppSettings.swift +++ /dev/null @@ -1,321 +0,0 @@ -// -// AppSettings.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 1/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import SwiftUI - -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") - } - } -} - -enum FontSize: Int { - case small = 0 - case medium = 1 - case large = 2 - case veryLarge = 3 -} - -final class AppSettings: ObservableObject { - - #if os(macOS) - static let store: UserDefaults = UserDefaults.standard - #endif - - #if os(iOS) - static let store: UserDefaults = { - let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String - let suiteName = "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)" - return UserDefaults.init(suiteName: suiteName)! - }() - #endif - - public static let shared = AppSettings() - private init() {} - - struct Key { - - // Shared Defaults - static let refreshInterval = "refreshInterval" - static let hideDockUnreadCount = "JustinMillerHideDockUnreadCount" - static let activeExtensionPointIDs = "activeExtensionPointIDs" - static let lastImageCacheFlushDate = "lastImageCacheFlushDate" - static let firstRunDate = "firstRunDate" - static let lastRefresh = "lastRefresh" - static let addWebFeedAccountID = "addWebFeedAccountID" - static let addWebFeedFolderName = "addWebFeedFolderName" - static let addFolderAccountID = "addFolderAccountID" - static let timelineSortDirection = "timelineSortDirection" - - // iOS Defaults - static let userInterfaceColorPalette = "userInterfaceColorPalette" - static let timelineGroupByFeed = "timelineGroupByFeed" - static let refreshClearsReadArticles = "refreshClearsReadArticles" - static let timelineNumberOfLines = "timelineNumberOfLines" - static let timelineIconSize = "timelineIconSize" - static let articleFullscreenAvailable = "articleFullscreenAvailable" - static let articleFullscreenEnabled = "articleFullscreenEnabled" - static let confirmMarkAllAsRead = "confirmMarkAllAsRead" - - // macOS Defaults - static let windowState = "windowState" - static let sidebarFontSize = "sidebarFontSize" - static let timelineFontSize = "timelineFontSize" - static let detailFontSize = "detailFontSize" - static let openInBrowserInBackground = "openInBrowserInBackground" - static let importOPMLAccountID = "importOPMLAccountID" - static let exportOPMLAccountID = "exportOPMLAccountID" - static let defaultBrowserID = "defaultBrowserID" - - // Hidden macOS Defaults - static let showDebugMenu = "ShowDebugMenu" - static let timelineShowsSeparators = "CorreiaSeparators" - static let showTitleOnMainWindow = "KafasisTitleMode" - - #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 - - // MARK: Development Builds - let isDeveloperBuild: Bool = { - if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" { - return true - } - return false - }() - - // MARK: First Run Details - var firstRunDate: Date? { - set { - AppSettings.store.setValue(newValue, forKey: Key.firstRunDate) - objectWillChange.send() - } - get { - AppSettings.store.object(forKey: Key.firstRunDate) as? Date - } - } - - // MARK: Refresh Timings - @AppStorage(wrappedValue: RefreshInterval.everyHour, Key.refreshInterval, store: store) var refreshInterval: RefreshInterval - - // MARK: Dock Badge - @AppStorage(wrappedValue: false, Key.hideDockUnreadCount, store: store) var hideDockUnreadCount - - // MARK: Color Palette - var userInterfaceColorPalette: UserInterfaceColorPalette { - get { - if let palette = UserInterfaceColorPalette(rawValue: AppSettings.store.integer(forKey: Key.userInterfaceColorPalette)) { - return palette - } - return .automatic - } - set { - AppSettings.store.set(newValue.rawValue, forKey: Key.userInterfaceColorPalette) - objectWillChange.send() - } - } - - // MARK: Feeds & Folders - @AppStorage(Key.addWebFeedAccountID, store: store) var addWebFeedAccountID: String? - - @AppStorage(Key.addWebFeedFolderName, store: store) var addWebFeedFolderName: String? - - @AppStorage(Key.addFolderAccountID, store: store) var addFolderAccountID: String? - - @AppStorage(wrappedValue: false, Key.confirmMarkAllAsRead, store: store) var confirmMarkAllAsRead: Bool - - // MARK: Extension Points - var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { - get { - return AppSettings.store.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] - } - set { - UserDefaults.standard.set(newValue, forKey: Key.activeExtensionPointIDs) - objectWillChange.send() - } - } - - // MARK: Image Cache - var lastImageCacheFlushDate: Date? { - set { - AppSettings.store.setValue(newValue, forKey: Key.lastImageCacheFlushDate) - objectWillChange.send() - } - get { - AppSettings.store.object(forKey: Key.lastImageCacheFlushDate) as? Date - } - } - - // MARK: Timeline - @AppStorage(wrappedValue: false, Key.timelineGroupByFeed, store: store) var timelineGroupByFeed: Bool - - @AppStorage(wrappedValue: 3, Key.timelineNumberOfLines, store: store) var timelineNumberOfLines: Int { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: 40.0, Key.timelineIconSize, store: store) var timelineIconSize: Double { - didSet { - objectWillChange.send() - } - } - - /// Set to `true` to sort oldest to newest, `false` for newest to oldest. Default is `false`. - @AppStorage(wrappedValue: false, Key.timelineSortDirection, store: store) var timelineSortDirection: Bool - - // MARK: Refresh - @AppStorage(wrappedValue: false, Key.refreshClearsReadArticles, store: store) var refreshClearsReadArticles: Bool - - // MARK: Articles - @AppStorage(wrappedValue: false, Key.articleFullscreenAvailable, store: store) var articleFullscreenAvailable: Bool - - // MARK: Refresh - var lastRefresh: Date? { - set { - AppSettings.store.setValue(newValue, forKey: Key.lastRefresh) - objectWillChange.send() - } - get { - AppSettings.store.object(forKey: Key.lastRefresh) as? Date - } - } - - // MARK: Window State - var windowState: [AnyHashable : Any]? { - get { - return AppSettings.store.object(forKey: Key.windowState) as? [AnyHashable : Any] - } - set { - UserDefaults.standard.set(newValue, forKey: Key.windowState) - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.openInBrowserInBackground, store: store) var openInBrowserInBackground: Bool { - didSet { - objectWillChange.send() - } - } - - var sidebarFontSize: FontSize { - get { - return fontSize(for: Key.sidebarFontSize) - } - set { - AppSettings.store.set(newValue.rawValue, forKey: Key.sidebarFontSize) - objectWillChange.send() - } - } - - var timelineFontSize: FontSize { - get { - return fontSize(for: Key.timelineFontSize) - } - set { - AppSettings.store.set(newValue.rawValue, forKey: Key.timelineFontSize) - objectWillChange.send() - } - } - - var detailFontSize: FontSize { - get { - return fontSize(for: Key.detailFontSize) - } - set { - AppSettings.store.set(newValue.rawValue, forKey: Key.detailFontSize) - objectWillChange.send() - } - } - - @AppStorage(Key.importOPMLAccountID, store: store) var importOPMLAccountID: String? { - didSet { - objectWillChange.send() - } - } - - @AppStorage(Key.exportOPMLAccountID, store: store) var exportOPMLAccountID: String? { - didSet { - objectWillChange.send() - } - } - - @AppStorage(Key.defaultBrowserID, store: store) var defaultBrowserID: String? { - didSet { - objectWillChange.send() - } - } - - @AppStorage(Key.showTitleOnMainWindow, store: store) var showTitleOnMainWindow: Bool? { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.showDebugMenu, store: store) var showDebugMenu: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.timelineShowsSeparators, store: store) var timelineShowsSeparators: Bool { - didSet { - objectWillChange.send() - } - } - - #if !MAC_APP_STORE - @AppStorage(wrappedValue: false, Key.webInspectorEnabled, store: store) var webInspectorEnabled: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.webInspectorStartsAttached, store: store) var webInspectorStartsAttached: Bool { - didSet { - objectWillChange.send() - } - } - #endif - - -} - -extension AppSettings { - - func isFirstRun() -> Bool { - if let _ = AppSettings.store.object(forKey: Key.firstRunDate) as? Date { - return false - } - firstRunDate = Date() - return true - } - - func fontSize(for key: String) -> FontSize { - // Punted till after 1.0. - return .medium - } -} diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index 7fe88ed18..a6dfa2f3b 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -13,13 +13,13 @@ struct MainApp: App { #if os(macOS) @NSApplicationDelegateAdaptor(AppDelegate.self) private var delegate - let preferences = MacPreferences() #endif #if os(iOS) @UIApplicationDelegateAdaptor(AppDelegate.self) private var delegate #endif @StateObject private var sceneModel = SceneModel() + @StateObject private var defaults = AppDefaults.shared @SceneBuilder var body: some Scene { #if os(macOS) @@ -134,7 +134,7 @@ struct MainApp: App { .padding() .frame(width: 500) .navigationTitle("Preferences") - .environmentObject(preferences) + .environmentObject(defaults) } .windowToolbarStyle(UnifiedWindowToolbarStyle()) diff --git a/Multiplatform/iOS/AppDelegate.swift b/Multiplatform/iOS/AppDelegate.swift index 6124a75bb..3df75f60d 100644 --- a/Multiplatform/iOS/AppDelegate.swift +++ b/Multiplatform/iOS/AppDelegate.swift @@ -71,9 +71,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - AppDefaults.registerDefaults() + //AppDefaults.registerDefaults() - let isFirstRun = AppDefaults.isFirstRun + let isFirstRun = AppDefaults.shared.isFirstRun() if isFirstRun { os_log("Is first run.", log: log, type: .info) } @@ -139,7 +139,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } @objc func accountRefreshDidFinish(_ note: Notification) { - AppDefaults.lastRefresh = Date() + AppDefaults.shared.lastRefresh = Date() } // MARK: - API @@ -163,7 +163,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // extensionFeedAddRequestFile.resume() syncTimer?.update() - if let lastRefresh = AppDefaults.lastRefresh { + if let lastRefresh = AppDefaults.shared.lastRefresh { if Date() > lastRefresh.addingTimeInterval(15 * 60) { AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) } else { diff --git a/Multiplatform/iOS/Settings/SettingsView.swift b/Multiplatform/iOS/Settings/SettingsView.swift index 61cfb879f..b08cd249b 100644 --- a/Multiplatform/iOS/Settings/SettingsView.swift +++ b/Multiplatform/iOS/Settings/SettingsView.swift @@ -57,7 +57,7 @@ struct SettingsView: View { @Environment(\.presentationMode) var presentationMode @StateObject private var viewModel = SettingsViewModel() - @StateObject private var settings = AppSettings.shared + @StateObject private var settings = AppDefaults.shared var body: some View { NavigationView { diff --git a/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift b/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift index ce12a95a0..2b064917c 100644 --- a/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift +++ b/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift @@ -10,7 +10,7 @@ import SwiftUI struct TimelineLayoutView: View { - @EnvironmentObject private var appSettings: AppSettings + @EnvironmentObject private var appSettings: AppDefaults private let sampleTitle = "Lorem dolor sed viverra ipsum. Gravida rutrum quisque non tellus. Rutrum tellus pellentesque eu tincidunt tortor. Sed blandit libero volutpat sed cras ornare. Et netus et malesuada fames ac. Ultrices eros in cursus turpis massa tincidunt dui ut ornare. Lacus sed viverra tellus in. Sollicitudin ac orci phasellus egestas. Purus in mollis nunc sed. Sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque. Interdum consectetur libero id faucibus nisl tincidunt eget." @@ -33,7 +33,7 @@ struct TimelineLayoutView: View { } var iconSize: some View { - Slider(value: $appSettings.timelineIconSize, in: 20...60, minimumValueLabel: Text("Small"), maximumValueLabel: Text("Large"), label: { + Slider(value: $appSettings.timelineIconSize, in: 20...60, step: 10, minimumValueLabel: Text("Small"), maximumValueLabel: Text("Large"), label: { Text(String(appSettings.timelineIconSize)) }) } diff --git a/Multiplatform/macOS/AppDelegate.swift b/Multiplatform/macOS/AppDelegate.swift index 3d064ff87..e01a4f244 100644 --- a/Multiplatform/macOS/AppDelegate.swift +++ b/Multiplatform/macOS/AppDelegate.swift @@ -133,8 +133,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele } #endif - AppDefaults.registerDefaults() - let isFirstRun = AppDefaults.isFirstRun + //AppDefaults.registerDefaults() + let isFirstRun = AppDefaults.shared.isFirstRun() if isFirstRun { os_log(.debug, log: log, "Is first run.") } @@ -245,7 +245,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele // MARK: - Dock Badge @objc func updateDockBadge() { - let label = unreadCount > 0 && !AppDefaults.hideDockUnreadCount ? "\(unreadCount)" : "" + let label = unreadCount > 0 && !AppDefaults.shared.hideDockUnreadCount ? "\(unreadCount)" : "" NSApplication.shared.dockTile.badgeLabel = label } diff --git a/Multiplatform/macOS/Preferences/Views/MacPreferencesView.swift b/Multiplatform/macOS/Preferences/MacPreferencesView.swift similarity index 89% rename from Multiplatform/macOS/Preferences/Views/MacPreferencesView.swift rename to Multiplatform/macOS/Preferences/MacPreferencesView.swift index f7f70d0c3..606d7eb27 100644 --- a/Multiplatform/macOS/Preferences/Views/MacPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesView.swift @@ -32,19 +32,19 @@ struct MacPreferenceViewModel { struct MacPreferencesView: View { - @EnvironmentObject var preferences: MacPreferences + @EnvironmentObject var defaults: AppDefaults @State private var viewModel = MacPreferenceViewModel() var body: some View { VStack { if viewModel.currentPreferencePane == .general { - AnyView(GeneralPreferencesView()) + AnyView(GeneralPreferencesView().environmentObject(defaults)) } else if viewModel.currentPreferencePane == .accounts { - AnyView(AccountsPreferencesView()) + AnyView(AccountsPreferencesView().environmentObject(defaults)) } else { - AnyView(AdvancedPreferencesView(preferences: preferences)) + AnyView(AdvancedPreferencesView().environmentObject(defaults)) } } .toolbar { diff --git a/Multiplatform/macOS/Preferences/Model/MacPreferences.swift b/Multiplatform/macOS/Preferences/Model/MacPreferences.swift deleted file mode 100644 index 4c0d4e2e8..000000000 --- a/Multiplatform/macOS/Preferences/Model/MacPreferences.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// MacPreferences.swift -// macOS -// -// Created by Stuart Breckenridge on 27/6/20. -// - -import SwiftUI - -enum FontSize: Int { - case small = 0 - case medium = 1 - case large = 2 - case veryLarge = 3 -} - -/// The `MacPreferences` object stores all macOS specific user preferences. -class MacPreferences: ObservableObject { - - private struct AppKeys { - static let refreshInterval = "refreshInterval" - static let openInBackground = "openInBrowserInBackground" - static let showUnreadCountInDock = "showUnreadCountInDock" - static let checkForUpdatesAutomatically = "checkForAppUpdates" - static let downloadTestBuilds = "downloadTestBuilds" - static let sendCrashLogs = "sendCrashLogs" - } - - // Refresh Interval - public let refreshIntervals:[String] = RefreshFrequencies.allCases.map({ $0.description }) - @AppStorage(wrappedValue: 0, AppKeys.refreshInterval) var refreshFrequency { - didSet { - objectWillChange.send() - } - } - - // Open in background - @AppStorage(wrappedValue: false, AppKeys.openInBackground) var openInBackground { - didSet { - objectWillChange.send() - } - } - - // Unread Count in Dock - @AppStorage(wrappedValue: true, AppKeys.showUnreadCountInDock) var showUnreadCountInDock { - didSet { - objectWillChange.send() - } - } - - // Check for App Updates - @AppStorage(wrappedValue: true, AppKeys.checkForUpdatesAutomatically) var checkForUpdatesAutomatically { - didSet { - objectWillChange.send() - } - } - - // Test builds - @AppStorage(wrappedValue: false, AppKeys.downloadTestBuilds) var downloadTestBuilds { - didSet { - objectWillChange.send() - } - } - - // Crash Logs - @AppStorage(wrappedValue: false, AppKeys.sendCrashLogs) var sendCrashLogs { - didSet { - objectWillChange.send() - } - } -} - - -enum RefreshFrequencies: CaseIterable, CustomStringConvertible { - - case refreshEvery10Mins, refreshEvery20Mins, refreshHourly, refreshEvery2Hours, refreshEvery4Hours, refreshEvery8Hours, none - - var description: String { - switch self { - case .refreshEvery10Mins: - return "Every 10 minutes" - case .refreshEvery20Mins: - return "Every 20 minutes" - case .refreshHourly: - return "Every hour" - case .refreshEvery2Hours: - return "Every 2 hours" - case .refreshEvery4Hours: - return "Every 4 hours" - case .refreshEvery8Hours: - return "Every 8 hours" - case .none: - return "Manually" - } - } -} diff --git a/Multiplatform/macOS/Preferences/Views/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/View/AccountsPreferencesView.swift similarity index 100% rename from Multiplatform/macOS/Preferences/Views/AccountsPreferencesView.swift rename to Multiplatform/macOS/Preferences/View/AccountsPreferencesView.swift diff --git a/Multiplatform/macOS/Preferences/Views/AdvancedPreferencesView.swift b/Multiplatform/macOS/Preferences/View/AdvancedPreferencesView.swift similarity index 96% rename from Multiplatform/macOS/Preferences/Views/AdvancedPreferencesView.swift rename to Multiplatform/macOS/Preferences/View/AdvancedPreferencesView.swift index 2d2fba65f..e493f3582 100644 --- a/Multiplatform/macOS/Preferences/Views/AdvancedPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/View/AdvancedPreferencesView.swift @@ -9,7 +9,7 @@ import SwiftUI struct AdvancedPreferencesView: View { - @StateObject var preferences: MacPreferences + @EnvironmentObject private var preferences: AppDefaults var body: some View { VStack { diff --git a/Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift new file mode 100644 index 000000000..909e9a682 --- /dev/null +++ b/Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift @@ -0,0 +1,33 @@ +// +// GeneralPreferencesView.swift +// macOS +// +// Created by Stuart Breckenridge on 27/6/20. +// + +import SwiftUI + +struct GeneralPreferencesView: View { + + @EnvironmentObject private var defaults: AppDefaults + + var body: some View { + VStack { + Form { + Picker("Refresh Feeds", + selection: $defaults.interval, + content: { + ForEach(RefreshInterval.allCases, content: { interval in + Text(interval.description()).tag(interval.rawValue) + }) + }).frame(width: 300, alignment: .center) + + Toggle("Open webpages in background in browser", isOn: $defaults.openInBrowserInBackground) + + Toggle("Hide Unread Count in Dock", isOn: $defaults.hideDockUnreadCount) + } + Spacer() + }.frame(width: 300, alignment: .center) + } + +} diff --git a/Multiplatform/macOS/Preferences/Views/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/Views/GeneralPreferencesView.swift deleted file mode 100644 index a5bbd6e64..000000000 --- a/Multiplatform/macOS/Preferences/Views/GeneralPreferencesView.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// GeneralPreferencesView.swift -// macOS -// -// Created by Stuart Breckenridge on 27/6/20. -// - -import SwiftUI - -struct GeneralPreferencesView: View { - - @ObservedObject private var preferences = MacPreferences() - - var body: some View { - VStack { - Form { - Picker("Refresh Feeds", - selection: $preferences.refreshFrequency, - content: { - ForEach(0.. String { switch self { case .manually: From c2149579c98cbeb3ebd7a6f17a40460b03933fda Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Thu, 2 Jul 2020 09:57:36 +0800 Subject: [PATCH 5/7] Converts AppDefaults to singleton --- Multiplatform/Shared/AppDefaults.swift | 450 ++++++++++-------- Multiplatform/Shared/AppSettings.swift | 321 ------------- Multiplatform/Shared/MainApp.swift | 4 +- Multiplatform/iOS/AppDelegate.swift | 8 +- Multiplatform/iOS/Settings/SettingsView.swift | 2 +- .../Submenus/TimelineLayoutView.swift | 4 +- Multiplatform/macOS/AppDelegate.swift | 6 +- .../{Views => }/MacPreferencesView.swift | 8 +- .../Preferences/Model/MacPreferences.swift | 96 ---- .../AccountsPreferencesView.swift | 0 .../AdvancedPreferencesView.swift | 2 +- .../View/GeneralPreferencesView.swift | 33 ++ .../Views/GeneralPreferencesView.swift | 33 -- NetNewsWire.xcodeproj/project.pbxproj | 46 +- .../ExtensionPointManager.swift | 6 +- .../AddWebFeedDefaultContainer.swift | 10 +- Shared/Extensions/CacheCleaner.swift | 6 +- Shared/Timer/AccountRefreshTimer.swift | 4 +- Shared/Timer/RefreshInterval.swift | 4 +- 19 files changed, 336 insertions(+), 707 deletions(-) delete mode 100644 Multiplatform/Shared/AppSettings.swift rename Multiplatform/macOS/Preferences/{Views => }/MacPreferencesView.swift (89%) delete mode 100644 Multiplatform/macOS/Preferences/Model/MacPreferences.swift rename Multiplatform/macOS/Preferences/{Views => View}/AccountsPreferencesView.swift (100%) rename Multiplatform/macOS/Preferences/{Views => View}/AdvancedPreferencesView.swift (96%) create mode 100644 Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift delete mode 100644 Multiplatform/macOS/Preferences/Views/GeneralPreferencesView.swift diff --git a/Multiplatform/Shared/AppDefaults.swift b/Multiplatform/Shared/AppDefaults.swift index 265a16028..1031c7ef0 100644 --- a/Multiplatform/Shared/AppDefaults.swift +++ b/Multiplatform/Shared/AppDefaults.swift @@ -2,290 +2,352 @@ // AppDefaults.swift // NetNewsWire // -// Created by Maurice Parker on 6/28/20. +// Created by Stuart Breckenridge on 1/7/20. // Copyright © 2020 Ranchero Software. All rights reserved. // import Foundation +import SwiftUI +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") + } + } +} -struct AppDefaults { +enum FontSize: Int { + case small = 0 + case medium = 1 + case large = 2 + case veryLarge = 3 +} +final class AppDefaults: ObservableObject { + #if os(macOS) - static var shared: UserDefaults = UserDefaults.standard + static let store: UserDefaults = UserDefaults.standard #endif #if os(iOS) - static var shared: UserDefaults = { + static let store: UserDefaults = { let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String let suiteName = "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)" return UserDefaults.init(suiteName: suiteName)! }() #endif + public static let shared = AppDefaults() + private init() {} + struct Key { + + // Shared Defaults static let refreshInterval = "refreshInterval" static let hideDockUnreadCount = "JustinMillerHideDockUnreadCount" - static let userInterfaceColorPalette = "userInterfaceColorPalette" static let activeExtensionPointIDs = "activeExtensionPointIDs" static let lastImageCacheFlushDate = "lastImageCacheFlushDate" static let firstRunDate = "firstRunDate" - static let timelineGroupByFeed = "timelineGroupByFeed" - static let refreshClearsReadArticles = "refreshClearsReadArticles" - static let timelineNumberOfLines = "timelineNumberOfLines" - static let timelineIconSize = "timelineIconSize" - static let timelineSortDirection = "timelineSortDirection" - static let articleFullscreenAvailable = "articleFullscreenAvailable" - static let articleFullscreenEnabled = "articleFullscreenEnabled" - static let confirmMarkAllAsRead = "confirmMarkAllAsRead" static let lastRefresh = "lastRefresh" static let addWebFeedAccountID = "addWebFeedAccountID" static let addWebFeedFolderName = "addWebFeedFolderName" static let addFolderAccountID = "addFolderAccountID" + static let timelineSortDirection = "timelineSortDirection" + + // iOS Defaults + static let userInterfaceColorPalette = "userInterfaceColorPalette" + static let timelineGroupByFeed = "timelineGroupByFeed" + static let refreshClearsReadArticles = "refreshClearsReadArticles" + static let timelineNumberOfLines = "timelineNumberOfLines" + static let timelineIconSize = "timelineIconSize" + static let articleFullscreenAvailable = "articleFullscreenAvailable" + static let articleFullscreenEnabled = "articleFullscreenEnabled" + static let confirmMarkAllAsRead = "confirmMarkAllAsRead" + + // macOS Defaults + static let windowState = "windowState" + static let sidebarFontSize = "sidebarFontSize" + static let timelineFontSize = "timelineFontSize" + static let detailFontSize = "detailFontSize" + static let openInBrowserInBackground = "openInBrowserInBackground" + static let importOPMLAccountID = "importOPMLAccountID" + static let exportOPMLAccountID = "exportOPMLAccountID" + static let defaultBrowserID = "defaultBrowserID" + static let checkForUpdatesAutomatically = "checkForUpdatesAutomatically" + static let downloadTestBuilds = "downloadTestBuild" + static let sendCrashLogs = "sendCrashLogs" + + // Hidden macOS Defaults + static let showDebugMenu = "ShowDebugMenu" + static let timelineShowsSeparators = "CorreiaSeparators" + static let showTitleOnMainWindow = "KafasisTitleMode" + + #if !MAC_APP_STORE + static let webInspectorEnabled = "WebInspectorEnabled" + static let webInspectorStartsAttached = "__WebInspectorPageGroupLevel1__.WebKit2InspectorStartsAttached" + #endif + } - - static let isDeveloperBuild: Bool = { + + private static let smallestFontSizeRawValue = FontSize.small.rawValue + private static let largestFontSizeRawValue = FontSize.veryLarge.rawValue + + // MARK: Development Builds + let isDeveloperBuild: Bool = { if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" { return true } return false }() - - static let isFirstRun: Bool = { - if let _ = AppDefaults.shared.object(forKey: Key.firstRunDate) as? Date { - return false - } - firstRunDate = Date() - return true - }() - static var refreshInterval: RefreshInterval { - get { - let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval) - return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour - } + // MARK: First Run Details + var firstRunDate: Date? { set { - UserDefaults.standard.set(newValue.rawValue, forKey: Key.refreshInterval) + AppDefaults.store.setValue(newValue, forKey: Key.firstRunDate) + objectWillChange.send() + } + get { + AppDefaults.store.object(forKey: Key.firstRunDate) as? Date } } - - static var hideDockUnreadCount: Bool { - return bool(for: Key.hideDockUnreadCount) + + // MARK: Refresh Interval + @AppStorage(wrappedValue: 4, Key.refreshInterval, store: store) var interval: Int { + didSet { + objectWillChange.send() + } } - - static var userInterfaceColorPalette: UserInterfaceColorPalette { + + var refreshInterval: RefreshInterval { + RefreshInterval(rawValue: interval) ?? RefreshInterval.everyHour + } + + // MARK: Dock Badge + @AppStorage(wrappedValue: false, Key.hideDockUnreadCount, store: store) var hideDockUnreadCount { + didSet { + objectWillChange.send() + } + } + + // MARK: Color Palette + var userInterfaceColorPalette: UserInterfaceColorPalette { get { - if let result = UserInterfaceColorPalette(rawValue: int(for: Key.userInterfaceColorPalette)) { - return result + if let palette = UserInterfaceColorPalette(rawValue: AppDefaults.store.integer(forKey: Key.userInterfaceColorPalette)) { + return palette } return .automatic } set { - setInt(for: Key.userInterfaceColorPalette, newValue.rawValue) - } - } - - static var addWebFeedAccountID: String? { - get { - return string(for: Key.addWebFeedAccountID) - } - set { - setString(for: Key.addWebFeedAccountID, newValue) + AppDefaults.store.set(newValue.rawValue, forKey: Key.userInterfaceColorPalette) + objectWillChange.send() } } - static var addWebFeedFolderName: String? { - get { - return string(for: Key.addWebFeedFolderName) - } - set { - setString(for: Key.addWebFeedFolderName, newValue) - } - } + // MARK: Feeds & Folders + @AppStorage(Key.addWebFeedAccountID, store: store) var addWebFeedAccountID: String? - static var addFolderAccountID: String? { - get { - return string(for: Key.addFolderAccountID) - } - set { - setString(for: Key.addFolderAccountID, newValue) - } - } + @AppStorage(Key.addWebFeedFolderName, store: store) var addWebFeedFolderName: String? - static var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { + @AppStorage(Key.addFolderAccountID, store: store) var addFolderAccountID: String? + + @AppStorage(wrappedValue: false, Key.confirmMarkAllAsRead, store: store) var confirmMarkAllAsRead: Bool + + // MARK: Extension Points + var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { get { - return UserDefaults.standard.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] + return AppDefaults.store.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] } set { UserDefaults.standard.set(newValue, forKey: Key.activeExtensionPointIDs) + objectWillChange.send() } } - static var lastImageCacheFlushDate: Date? { - get { - return date(for: Key.lastImageCacheFlushDate) - } + // MARK: Image Cache + var lastImageCacheFlushDate: Date? { set { - setDate(for: Key.lastImageCacheFlushDate, newValue) + AppDefaults.store.setValue(newValue, forKey: Key.lastImageCacheFlushDate) + objectWillChange.send() } - } - - static var timelineGroupByFeed: Bool { get { - return bool(for: Key.timelineGroupByFeed) - } - set { - setBool(for: Key.timelineGroupByFeed, newValue) - } - } - - static var refreshClearsReadArticles: Bool { - get { - return bool(for: Key.refreshClearsReadArticles) - } - set { - setBool(for: Key.refreshClearsReadArticles, newValue) - } - } - - static var timelineSortDirection: ComparisonResult { - get { - return sortDirection(for: Key.timelineSortDirection) - } - set { - setSortDirection(for: Key.timelineSortDirection, newValue) - } - } - - static var articleFullscreenAvailable: Bool { - get { - return bool(for: Key.articleFullscreenAvailable) - } - set { - setBool(for: Key.articleFullscreenAvailable, newValue) - } - } - - static var articleFullscreenEnabled: Bool { - get { - return bool(for: Key.articleFullscreenEnabled) - } - set { - setBool(for: Key.articleFullscreenEnabled, newValue) - } - } - - static var confirmMarkAllAsRead: Bool { - get { - return bool(for: Key.confirmMarkAllAsRead) - } - set { - setBool(for: Key.confirmMarkAllAsRead, newValue) + AppDefaults.store.object(forKey: Key.lastImageCacheFlushDate) as? Date } } - static var lastRefresh: Date? { + // MARK: Timeline + @AppStorage(wrappedValue: false, Key.timelineGroupByFeed, store: store) var timelineGroupByFeed: Bool + + @AppStorage(wrappedValue: 3, Key.timelineNumberOfLines, store: store) var timelineNumberOfLines: Int { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: 40.0, Key.timelineIconSize, store: store) var timelineIconSize: Double { + didSet { + objectWillChange.send() + } + } + + /// Set to `true` to sort oldest to newest, `false` for newest to oldest. Default is `false`. + @AppStorage(wrappedValue: false, Key.timelineSortDirection, store: store) var timelineSortDirection: Bool + + // MARK: Refresh + @AppStorage(wrappedValue: false, Key.refreshClearsReadArticles, store: store) var refreshClearsReadArticles: Bool + + // MARK: Articles + @AppStorage(wrappedValue: false, Key.articleFullscreenAvailable, store: store) var articleFullscreenAvailable: Bool + + // MARK: Refresh + var lastRefresh: Date? { + set { + AppDefaults.store.setValue(newValue, forKey: Key.lastRefresh) + objectWillChange.send() + } get { - return date(for: Key.lastRefresh) + AppDefaults.store.object(forKey: Key.lastRefresh) as? Date + } + } + + // MARK: Window State + var windowState: [AnyHashable : Any]? { + get { + return AppDefaults.store.object(forKey: Key.windowState) as? [AnyHashable : Any] } set { - setDate(for: Key.lastRefresh, newValue) + UserDefaults.standard.set(newValue, forKey: Key.windowState) + objectWillChange.send() } } - static var timelineNumberOfLines: Int { + @AppStorage(wrappedValue: false, Key.openInBrowserInBackground, store: store) var openInBrowserInBackground: Bool { + didSet { + objectWillChange.send() + } + } + + var sidebarFontSize: FontSize { get { - return int(for: Key.timelineNumberOfLines) + return fontSize(for: Key.sidebarFontSize) } set { - setInt(for: Key.timelineNumberOfLines, newValue) + AppDefaults.store.set(newValue.rawValue, forKey: Key.sidebarFontSize) + objectWillChange.send() } } - static var timelineIconSize: IconSize { + var timelineFontSize: FontSize { get { - let rawValue = AppDefaults.shared.integer(forKey: Key.timelineIconSize) - return IconSize(rawValue: rawValue) ?? IconSize.medium + return fontSize(for: Key.timelineFontSize) } set { - AppDefaults.shared.set(newValue.rawValue, forKey: Key.timelineIconSize) + AppDefaults.store.set(newValue.rawValue, forKey: Key.timelineFontSize) + objectWillChange.send() } } - static func registerDefaults() { - let defaults: [String : Any] = [Key.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue, - Key.timelineGroupByFeed: false, - Key.refreshClearsReadArticles: false, - Key.timelineNumberOfLines: 2, - Key.timelineIconSize: IconSize.medium.rawValue, - Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, - Key.articleFullscreenAvailable: false, - Key.articleFullscreenEnabled: false, - Key.confirmMarkAllAsRead: true] - AppDefaults.shared.register(defaults: defaults) - } - -} - -private extension AppDefaults { - - static var firstRunDate: Date? { + var detailFontSize: FontSize { get { - return date(for: Key.firstRunDate) + return fontSize(for: Key.detailFontSize) } set { - setDate(for: Key.firstRunDate, newValue) + AppDefaults.store.set(newValue.rawValue, forKey: Key.detailFontSize) + objectWillChange.send() } } - - static func string(for key: String) -> String? { - return AppDefaults.shared.string(forKey: key) - } - static func setString(for key: String, _ value: String?) { - AppDefaults.shared.set(value, forKey: key) - } - - static func bool(for key: String) -> Bool { - return AppDefaults.shared.bool(forKey: key) - } - - static func setBool(for key: String, _ flag: Bool) { - AppDefaults.shared.set(flag, forKey: key) - } - - static func int(for key: String) -> Int { - return AppDefaults.shared.integer(forKey: key) - } - - static func setInt(for key: String, _ x: Int) { - AppDefaults.shared.set(x, forKey: key) - } - - static func date(for key: String) -> Date? { - return AppDefaults.shared.object(forKey: key) as? Date - } - - static func setDate(for key: String, _ date: Date?) { - AppDefaults.shared.set(date, forKey: key) - } - - static func sortDirection(for key:String) -> ComparisonResult { - let rawInt = int(for: key) - if rawInt == ComparisonResult.orderedAscending.rawValue { - return .orderedAscending + @AppStorage(Key.importOPMLAccountID, store: store) var importOPMLAccountID: String? { + didSet { + objectWillChange.send() } - return .orderedDescending } - - static func setSortDirection(for key: String, _ value: ComparisonResult) { - if value == .orderedAscending { - setInt(for: key, ComparisonResult.orderedAscending.rawValue) + + @AppStorage(Key.exportOPMLAccountID, store: store) var exportOPMLAccountID: String? { + didSet { + objectWillChange.send() } - else { - setInt(for: key, ComparisonResult.orderedDescending.rawValue) + } + + @AppStorage(Key.defaultBrowserID, store: store) var defaultBrowserID: String? { + didSet { + objectWillChange.send() + } + } + + @AppStorage(Key.showTitleOnMainWindow, store: store) var showTitleOnMainWindow: Bool? { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.showDebugMenu, store: store) var showDebugMenu: Bool { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.timelineShowsSeparators, store: store) var timelineShowsSeparators: Bool { + didSet { + objectWillChange.send() + } + } + + #if !MAC_APP_STORE + @AppStorage(wrappedValue: false, Key.webInspectorEnabled, store: store) var webInspectorEnabled: Bool { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.webInspectorStartsAttached, store: store) var webInspectorStartsAttached: Bool { + didSet { + objectWillChange.send() + } + } + #endif + + @AppStorage(wrappedValue: true, Key.checkForUpdatesAutomatically, store: store) var checkForUpdatesAutomatically: Bool { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: false, Key.downloadTestBuilds, store: store) var downloadTestBuilds: Bool { + didSet { + objectWillChange.send() + } + } + + @AppStorage(wrappedValue: true, Key.sendCrashLogs, store: store) var sendCrashLogs: Bool { + didSet { + objectWillChange.send() } } } + +extension AppDefaults { + + func isFirstRun() -> Bool { + if let _ = AppDefaults.store.object(forKey: Key.firstRunDate) as? Date { + return false + } + firstRunDate = Date() + return true + } + + func fontSize(for key: String) -> FontSize { + // Punted till after 1.0. + return .medium + } +} diff --git a/Multiplatform/Shared/AppSettings.swift b/Multiplatform/Shared/AppSettings.swift deleted file mode 100644 index a584034c2..000000000 --- a/Multiplatform/Shared/AppSettings.swift +++ /dev/null @@ -1,321 +0,0 @@ -// -// AppSettings.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 1/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import SwiftUI - -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") - } - } -} - -enum FontSize: Int { - case small = 0 - case medium = 1 - case large = 2 - case veryLarge = 3 -} - -final class AppSettings: ObservableObject { - - #if os(macOS) - static let store: UserDefaults = UserDefaults.standard - #endif - - #if os(iOS) - static let store: UserDefaults = { - let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String - let suiteName = "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)" - return UserDefaults.init(suiteName: suiteName)! - }() - #endif - - public static let shared = AppSettings() - private init() {} - - struct Key { - - // Shared Defaults - static let refreshInterval = "refreshInterval" - static let hideDockUnreadCount = "JustinMillerHideDockUnreadCount" - static let activeExtensionPointIDs = "activeExtensionPointIDs" - static let lastImageCacheFlushDate = "lastImageCacheFlushDate" - static let firstRunDate = "firstRunDate" - static let lastRefresh = "lastRefresh" - static let addWebFeedAccountID = "addWebFeedAccountID" - static let addWebFeedFolderName = "addWebFeedFolderName" - static let addFolderAccountID = "addFolderAccountID" - static let timelineSortDirection = "timelineSortDirection" - - // iOS Defaults - static let userInterfaceColorPalette = "userInterfaceColorPalette" - static let timelineGroupByFeed = "timelineGroupByFeed" - static let refreshClearsReadArticles = "refreshClearsReadArticles" - static let timelineNumberOfLines = "timelineNumberOfLines" - static let timelineIconSize = "timelineIconSize" - static let articleFullscreenAvailable = "articleFullscreenAvailable" - static let articleFullscreenEnabled = "articleFullscreenEnabled" - static let confirmMarkAllAsRead = "confirmMarkAllAsRead" - - // macOS Defaults - static let windowState = "windowState" - static let sidebarFontSize = "sidebarFontSize" - static let timelineFontSize = "timelineFontSize" - static let detailFontSize = "detailFontSize" - static let openInBrowserInBackground = "openInBrowserInBackground" - static let importOPMLAccountID = "importOPMLAccountID" - static let exportOPMLAccountID = "exportOPMLAccountID" - static let defaultBrowserID = "defaultBrowserID" - - // Hidden macOS Defaults - static let showDebugMenu = "ShowDebugMenu" - static let timelineShowsSeparators = "CorreiaSeparators" - static let showTitleOnMainWindow = "KafasisTitleMode" - - #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 - - // MARK: Development Builds - let isDeveloperBuild: Bool = { - if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" { - return true - } - return false - }() - - // MARK: First Run Details - var firstRunDate: Date? { - set { - AppSettings.store.setValue(newValue, forKey: Key.firstRunDate) - objectWillChange.send() - } - get { - AppSettings.store.object(forKey: Key.firstRunDate) as? Date - } - } - - // MARK: Refresh Timings - @AppStorage(wrappedValue: RefreshInterval.everyHour, Key.refreshInterval, store: store) var refreshInterval: RefreshInterval - - // MARK: Dock Badge - @AppStorage(wrappedValue: false, Key.hideDockUnreadCount, store: store) var hideDockUnreadCount - - // MARK: Color Palette - var userInterfaceColorPalette: UserInterfaceColorPalette { - get { - if let palette = UserInterfaceColorPalette(rawValue: AppSettings.store.integer(forKey: Key.userInterfaceColorPalette)) { - return palette - } - return .automatic - } - set { - AppSettings.store.set(newValue.rawValue, forKey: Key.userInterfaceColorPalette) - objectWillChange.send() - } - } - - // MARK: Feeds & Folders - @AppStorage(Key.addWebFeedAccountID, store: store) var addWebFeedAccountID: String? - - @AppStorage(Key.addWebFeedFolderName, store: store) var addWebFeedFolderName: String? - - @AppStorage(Key.addFolderAccountID, store: store) var addFolderAccountID: String? - - @AppStorage(wrappedValue: false, Key.confirmMarkAllAsRead, store: store) var confirmMarkAllAsRead: Bool - - // MARK: Extension Points - var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { - get { - return AppSettings.store.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] - } - set { - UserDefaults.standard.set(newValue, forKey: Key.activeExtensionPointIDs) - objectWillChange.send() - } - } - - // MARK: Image Cache - var lastImageCacheFlushDate: Date? { - set { - AppSettings.store.setValue(newValue, forKey: Key.lastImageCacheFlushDate) - objectWillChange.send() - } - get { - AppSettings.store.object(forKey: Key.lastImageCacheFlushDate) as? Date - } - } - - // MARK: Timeline - @AppStorage(wrappedValue: false, Key.timelineGroupByFeed, store: store) var timelineGroupByFeed: Bool - - @AppStorage(wrappedValue: 3, Key.timelineNumberOfLines, store: store) var timelineNumberOfLines: Int { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: 40.0, Key.timelineIconSize, store: store) var timelineIconSize: Double { - didSet { - objectWillChange.send() - } - } - - /// Set to `true` to sort oldest to newest, `false` for newest to oldest. Default is `false`. - @AppStorage(wrappedValue: false, Key.timelineSortDirection, store: store) var timelineSortDirection: Bool - - // MARK: Refresh - @AppStorage(wrappedValue: false, Key.refreshClearsReadArticles, store: store) var refreshClearsReadArticles: Bool - - // MARK: Articles - @AppStorage(wrappedValue: false, Key.articleFullscreenAvailable, store: store) var articleFullscreenAvailable: Bool - - // MARK: Refresh - var lastRefresh: Date? { - set { - AppSettings.store.setValue(newValue, forKey: Key.lastRefresh) - objectWillChange.send() - } - get { - AppSettings.store.object(forKey: Key.lastRefresh) as? Date - } - } - - // MARK: Window State - var windowState: [AnyHashable : Any]? { - get { - return AppSettings.store.object(forKey: Key.windowState) as? [AnyHashable : Any] - } - set { - UserDefaults.standard.set(newValue, forKey: Key.windowState) - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.openInBrowserInBackground, store: store) var openInBrowserInBackground: Bool { - didSet { - objectWillChange.send() - } - } - - var sidebarFontSize: FontSize { - get { - return fontSize(for: Key.sidebarFontSize) - } - set { - AppSettings.store.set(newValue.rawValue, forKey: Key.sidebarFontSize) - objectWillChange.send() - } - } - - var timelineFontSize: FontSize { - get { - return fontSize(for: Key.timelineFontSize) - } - set { - AppSettings.store.set(newValue.rawValue, forKey: Key.timelineFontSize) - objectWillChange.send() - } - } - - var detailFontSize: FontSize { - get { - return fontSize(for: Key.detailFontSize) - } - set { - AppSettings.store.set(newValue.rawValue, forKey: Key.detailFontSize) - objectWillChange.send() - } - } - - @AppStorage(Key.importOPMLAccountID, store: store) var importOPMLAccountID: String? { - didSet { - objectWillChange.send() - } - } - - @AppStorage(Key.exportOPMLAccountID, store: store) var exportOPMLAccountID: String? { - didSet { - objectWillChange.send() - } - } - - @AppStorage(Key.defaultBrowserID, store: store) var defaultBrowserID: String? { - didSet { - objectWillChange.send() - } - } - - @AppStorage(Key.showTitleOnMainWindow, store: store) var showTitleOnMainWindow: Bool? { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.showDebugMenu, store: store) var showDebugMenu: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.timelineShowsSeparators, store: store) var timelineShowsSeparators: Bool { - didSet { - objectWillChange.send() - } - } - - #if !MAC_APP_STORE - @AppStorage(wrappedValue: false, Key.webInspectorEnabled, store: store) var webInspectorEnabled: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.webInspectorStartsAttached, store: store) var webInspectorStartsAttached: Bool { - didSet { - objectWillChange.send() - } - } - #endif - - -} - -extension AppSettings { - - func isFirstRun() -> Bool { - if let _ = AppSettings.store.object(forKey: Key.firstRunDate) as? Date { - return false - } - firstRunDate = Date() - return true - } - - func fontSize(for key: String) -> FontSize { - // Punted till after 1.0. - return .medium - } -} diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index 7fe88ed18..a6dfa2f3b 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -13,13 +13,13 @@ struct MainApp: App { #if os(macOS) @NSApplicationDelegateAdaptor(AppDelegate.self) private var delegate - let preferences = MacPreferences() #endif #if os(iOS) @UIApplicationDelegateAdaptor(AppDelegate.self) private var delegate #endif @StateObject private var sceneModel = SceneModel() + @StateObject private var defaults = AppDefaults.shared @SceneBuilder var body: some Scene { #if os(macOS) @@ -134,7 +134,7 @@ struct MainApp: App { .padding() .frame(width: 500) .navigationTitle("Preferences") - .environmentObject(preferences) + .environmentObject(defaults) } .windowToolbarStyle(UnifiedWindowToolbarStyle()) diff --git a/Multiplatform/iOS/AppDelegate.swift b/Multiplatform/iOS/AppDelegate.swift index 6124a75bb..3df75f60d 100644 --- a/Multiplatform/iOS/AppDelegate.swift +++ b/Multiplatform/iOS/AppDelegate.swift @@ -71,9 +71,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - AppDefaults.registerDefaults() + //AppDefaults.registerDefaults() - let isFirstRun = AppDefaults.isFirstRun + let isFirstRun = AppDefaults.shared.isFirstRun() if isFirstRun { os_log("Is first run.", log: log, type: .info) } @@ -139,7 +139,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } @objc func accountRefreshDidFinish(_ note: Notification) { - AppDefaults.lastRefresh = Date() + AppDefaults.shared.lastRefresh = Date() } // MARK: - API @@ -163,7 +163,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // extensionFeedAddRequestFile.resume() syncTimer?.update() - if let lastRefresh = AppDefaults.lastRefresh { + if let lastRefresh = AppDefaults.shared.lastRefresh { if Date() > lastRefresh.addingTimeInterval(15 * 60) { AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) } else { diff --git a/Multiplatform/iOS/Settings/SettingsView.swift b/Multiplatform/iOS/Settings/SettingsView.swift index 61cfb879f..b08cd249b 100644 --- a/Multiplatform/iOS/Settings/SettingsView.swift +++ b/Multiplatform/iOS/Settings/SettingsView.swift @@ -57,7 +57,7 @@ struct SettingsView: View { @Environment(\.presentationMode) var presentationMode @StateObject private var viewModel = SettingsViewModel() - @StateObject private var settings = AppSettings.shared + @StateObject private var settings = AppDefaults.shared var body: some View { NavigationView { diff --git a/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift b/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift index ce12a95a0..2b064917c 100644 --- a/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift +++ b/Multiplatform/iOS/Settings/Submenus/TimelineLayoutView.swift @@ -10,7 +10,7 @@ import SwiftUI struct TimelineLayoutView: View { - @EnvironmentObject private var appSettings: AppSettings + @EnvironmentObject private var appSettings: AppDefaults private let sampleTitle = "Lorem dolor sed viverra ipsum. Gravida rutrum quisque non tellus. Rutrum tellus pellentesque eu tincidunt tortor. Sed blandit libero volutpat sed cras ornare. Et netus et malesuada fames ac. Ultrices eros in cursus turpis massa tincidunt dui ut ornare. Lacus sed viverra tellus in. Sollicitudin ac orci phasellus egestas. Purus in mollis nunc sed. Sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque. Interdum consectetur libero id faucibus nisl tincidunt eget." @@ -33,7 +33,7 @@ struct TimelineLayoutView: View { } var iconSize: some View { - Slider(value: $appSettings.timelineIconSize, in: 20...60, minimumValueLabel: Text("Small"), maximumValueLabel: Text("Large"), label: { + Slider(value: $appSettings.timelineIconSize, in: 20...60, step: 10, minimumValueLabel: Text("Small"), maximumValueLabel: Text("Large"), label: { Text(String(appSettings.timelineIconSize)) }) } diff --git a/Multiplatform/macOS/AppDelegate.swift b/Multiplatform/macOS/AppDelegate.swift index 3d064ff87..e01a4f244 100644 --- a/Multiplatform/macOS/AppDelegate.swift +++ b/Multiplatform/macOS/AppDelegate.swift @@ -133,8 +133,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele } #endif - AppDefaults.registerDefaults() - let isFirstRun = AppDefaults.isFirstRun + //AppDefaults.registerDefaults() + let isFirstRun = AppDefaults.shared.isFirstRun() if isFirstRun { os_log(.debug, log: log, "Is first run.") } @@ -245,7 +245,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele // MARK: - Dock Badge @objc func updateDockBadge() { - let label = unreadCount > 0 && !AppDefaults.hideDockUnreadCount ? "\(unreadCount)" : "" + let label = unreadCount > 0 && !AppDefaults.shared.hideDockUnreadCount ? "\(unreadCount)" : "" NSApplication.shared.dockTile.badgeLabel = label } diff --git a/Multiplatform/macOS/Preferences/Views/MacPreferencesView.swift b/Multiplatform/macOS/Preferences/MacPreferencesView.swift similarity index 89% rename from Multiplatform/macOS/Preferences/Views/MacPreferencesView.swift rename to Multiplatform/macOS/Preferences/MacPreferencesView.swift index f7f70d0c3..606d7eb27 100644 --- a/Multiplatform/macOS/Preferences/Views/MacPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesView.swift @@ -32,19 +32,19 @@ struct MacPreferenceViewModel { struct MacPreferencesView: View { - @EnvironmentObject var preferences: MacPreferences + @EnvironmentObject var defaults: AppDefaults @State private var viewModel = MacPreferenceViewModel() var body: some View { VStack { if viewModel.currentPreferencePane == .general { - AnyView(GeneralPreferencesView()) + AnyView(GeneralPreferencesView().environmentObject(defaults)) } else if viewModel.currentPreferencePane == .accounts { - AnyView(AccountsPreferencesView()) + AnyView(AccountsPreferencesView().environmentObject(defaults)) } else { - AnyView(AdvancedPreferencesView(preferences: preferences)) + AnyView(AdvancedPreferencesView().environmentObject(defaults)) } } .toolbar { diff --git a/Multiplatform/macOS/Preferences/Model/MacPreferences.swift b/Multiplatform/macOS/Preferences/Model/MacPreferences.swift deleted file mode 100644 index 4c0d4e2e8..000000000 --- a/Multiplatform/macOS/Preferences/Model/MacPreferences.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// MacPreferences.swift -// macOS -// -// Created by Stuart Breckenridge on 27/6/20. -// - -import SwiftUI - -enum FontSize: Int { - case small = 0 - case medium = 1 - case large = 2 - case veryLarge = 3 -} - -/// The `MacPreferences` object stores all macOS specific user preferences. -class MacPreferences: ObservableObject { - - private struct AppKeys { - static let refreshInterval = "refreshInterval" - static let openInBackground = "openInBrowserInBackground" - static let showUnreadCountInDock = "showUnreadCountInDock" - static let checkForUpdatesAutomatically = "checkForAppUpdates" - static let downloadTestBuilds = "downloadTestBuilds" - static let sendCrashLogs = "sendCrashLogs" - } - - // Refresh Interval - public let refreshIntervals:[String] = RefreshFrequencies.allCases.map({ $0.description }) - @AppStorage(wrappedValue: 0, AppKeys.refreshInterval) var refreshFrequency { - didSet { - objectWillChange.send() - } - } - - // Open in background - @AppStorage(wrappedValue: false, AppKeys.openInBackground) var openInBackground { - didSet { - objectWillChange.send() - } - } - - // Unread Count in Dock - @AppStorage(wrappedValue: true, AppKeys.showUnreadCountInDock) var showUnreadCountInDock { - didSet { - objectWillChange.send() - } - } - - // Check for App Updates - @AppStorage(wrappedValue: true, AppKeys.checkForUpdatesAutomatically) var checkForUpdatesAutomatically { - didSet { - objectWillChange.send() - } - } - - // Test builds - @AppStorage(wrappedValue: false, AppKeys.downloadTestBuilds) var downloadTestBuilds { - didSet { - objectWillChange.send() - } - } - - // Crash Logs - @AppStorage(wrappedValue: false, AppKeys.sendCrashLogs) var sendCrashLogs { - didSet { - objectWillChange.send() - } - } -} - - -enum RefreshFrequencies: CaseIterable, CustomStringConvertible { - - case refreshEvery10Mins, refreshEvery20Mins, refreshHourly, refreshEvery2Hours, refreshEvery4Hours, refreshEvery8Hours, none - - var description: String { - switch self { - case .refreshEvery10Mins: - return "Every 10 minutes" - case .refreshEvery20Mins: - return "Every 20 minutes" - case .refreshHourly: - return "Every hour" - case .refreshEvery2Hours: - return "Every 2 hours" - case .refreshEvery4Hours: - return "Every 4 hours" - case .refreshEvery8Hours: - return "Every 8 hours" - case .none: - return "Manually" - } - } -} diff --git a/Multiplatform/macOS/Preferences/Views/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/View/AccountsPreferencesView.swift similarity index 100% rename from Multiplatform/macOS/Preferences/Views/AccountsPreferencesView.swift rename to Multiplatform/macOS/Preferences/View/AccountsPreferencesView.swift diff --git a/Multiplatform/macOS/Preferences/Views/AdvancedPreferencesView.swift b/Multiplatform/macOS/Preferences/View/AdvancedPreferencesView.swift similarity index 96% rename from Multiplatform/macOS/Preferences/Views/AdvancedPreferencesView.swift rename to Multiplatform/macOS/Preferences/View/AdvancedPreferencesView.swift index 2d2fba65f..e493f3582 100644 --- a/Multiplatform/macOS/Preferences/Views/AdvancedPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/View/AdvancedPreferencesView.swift @@ -9,7 +9,7 @@ import SwiftUI struct AdvancedPreferencesView: View { - @StateObject var preferences: MacPreferences + @EnvironmentObject private var preferences: AppDefaults var body: some View { VStack { diff --git a/Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift new file mode 100644 index 000000000..909e9a682 --- /dev/null +++ b/Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift @@ -0,0 +1,33 @@ +// +// GeneralPreferencesView.swift +// macOS +// +// Created by Stuart Breckenridge on 27/6/20. +// + +import SwiftUI + +struct GeneralPreferencesView: View { + + @EnvironmentObject private var defaults: AppDefaults + + var body: some View { + VStack { + Form { + Picker("Refresh Feeds", + selection: $defaults.interval, + content: { + ForEach(RefreshInterval.allCases, content: { interval in + Text(interval.description()).tag(interval.rawValue) + }) + }).frame(width: 300, alignment: .center) + + Toggle("Open webpages in background in browser", isOn: $defaults.openInBrowserInBackground) + + Toggle("Hide Unread Count in Dock", isOn: $defaults.hideDockUnreadCount) + } + Spacer() + }.frame(width: 300, alignment: .center) + } + +} diff --git a/Multiplatform/macOS/Preferences/Views/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/Views/GeneralPreferencesView.swift deleted file mode 100644 index a5bbd6e64..000000000 --- a/Multiplatform/macOS/Preferences/Views/GeneralPreferencesView.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// GeneralPreferencesView.swift -// macOS -// -// Created by Stuart Breckenridge on 27/6/20. -// - -import SwiftUI - -struct GeneralPreferencesView: View { - - @ObservedObject private var preferences = MacPreferences() - - var body: some View { - VStack { - Form { - Picker("Refresh Feeds", - selection: $preferences.refreshFrequency, - content: { - ForEach(0.. String { switch self { case .manually: From f92b219cdca91c401196895ef6ae95bce57c07bd Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Thu, 2 Jul 2020 10:47:45 +0800 Subject: [PATCH 6/7] NetNewsWire-iOS AppDefaults is now a singleton --- iOS/Add/AddFolderViewController.swift | 4 +- iOS/AppDefaults.swift | 107 +++++++++--------- iOS/AppDelegate.swift | 6 +- iOS/Article/ArticleViewController.swift | 6 +- iOS/Article/WebViewController.swift | 10 +- .../MarkAsReadAlertController.swift | 2 +- .../MasterTimelineViewController.swift | 10 +- iOS/SceneCoordinator.swift | 10 +- iOS/Settings/AddAccountViewController.swift | 2 +- iOS/Settings/SettingsViewController.swift | 30 ++--- .../TimelineCustomizerViewController.swift | 8 +- .../TimelinePreviewTableViewController.swift | 2 +- .../ShareDefaultContainer.swift | 10 +- 13 files changed, 105 insertions(+), 102 deletions(-) diff --git a/iOS/Add/AddFolderViewController.swift b/iOS/Add/AddFolderViewController.swift index 1500d84b8..7eebbd69b 100644 --- a/iOS/Add/AddFolderViewController.swift +++ b/iOS/Add/AddFolderViewController.swift @@ -22,7 +22,7 @@ class AddFolderViewController: UITableViewController, AddContainerViewController private var accounts: [Account]! { didSet { - if let predefinedAccount = accounts.first(where: { $0.accountID == AppDefaults.addFolderAccountID }) { + if let predefinedAccount = accounts.first(where: { $0.accountID == AppDefaults.shared.addFolderAccountID }) { selectedAccount = predefinedAccount } else { selectedAccount = accounts[0] @@ -69,7 +69,7 @@ class AddFolderViewController: UITableViewController, AddContainerViewController } private func didSelect(_ account: Account) { - AppDefaults.addFolderAccountID = account.accountID + AppDefaults.shared.addFolderAccountID = account.accountID selectedAccount = account } diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index ad7833afb..9f6c757f4 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -26,9 +26,12 @@ enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable { } -struct AppDefaults { +class AppDefaults { - static var shared: UserDefaults = { + 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)! @@ -53,15 +56,15 @@ struct AppDefaults { static let addFolderAccountID = "addFolderAccountID" } - static let isDeveloperBuild: Bool = { + let isDeveloperBuild: Bool = { if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" { return true } return false }() - static let isFirstRun: Bool = { - if let _ = AppDefaults.shared.object(forKey: Key.firstRunDate) as? Date { + let isFirstRun: Bool = { + if let _ = AppDefaults.store.object(forKey: Key.firstRunDate) as? Date { return false } firstRunDate = Date() @@ -80,34 +83,34 @@ struct AppDefaults { } } - static var addWebFeedAccountID: String? { + var addWebFeedAccountID: String? { get { - return string(for: Key.addWebFeedAccountID) + return AppDefaults.string(for: Key.addWebFeedAccountID) } set { - setString(for: Key.addWebFeedAccountID, newValue) + AppDefaults.setString(for: Key.addWebFeedAccountID, newValue) } } - static var addWebFeedFolderName: String? { + var addWebFeedFolderName: String? { get { - return string(for: Key.addWebFeedFolderName) + return AppDefaults.string(for: Key.addWebFeedFolderName) } set { - setString(for: Key.addWebFeedFolderName, newValue) + AppDefaults.setString(for: Key.addWebFeedFolderName, newValue) } } - static var addFolderAccountID: String? { + var addFolderAccountID: String? { get { - return string(for: Key.addFolderAccountID) + return AppDefaults.string(for: Key.addFolderAccountID) } set { - setString(for: Key.addFolderAccountID, newValue) + AppDefaults.setString(for: Key.addFolderAccountID, newValue) } } - static var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { + var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { get { return UserDefaults.standard.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] } @@ -116,94 +119,94 @@ struct AppDefaults { } } - static var lastImageCacheFlushDate: Date? { + var lastImageCacheFlushDate: Date? { get { - return date(for: Key.lastImageCacheFlushDate) + return AppDefaults.date(for: Key.lastImageCacheFlushDate) } set { - setDate(for: Key.lastImageCacheFlushDate, newValue) + AppDefaults.setDate(for: Key.lastImageCacheFlushDate, newValue) } } - static var timelineGroupByFeed: Bool { + var timelineGroupByFeed: Bool { get { - return bool(for: Key.timelineGroupByFeed) + return AppDefaults.bool(for: Key.timelineGroupByFeed) } set { - setBool(for: Key.timelineGroupByFeed, newValue) + AppDefaults.setBool(for: Key.timelineGroupByFeed, newValue) } } - static var refreshClearsReadArticles: Bool { + var refreshClearsReadArticles: Bool { get { - return bool(for: Key.refreshClearsReadArticles) + return AppDefaults.bool(for: Key.refreshClearsReadArticles) } set { - setBool(for: Key.refreshClearsReadArticles, newValue) + AppDefaults.setBool(for: Key.refreshClearsReadArticles, newValue) } } - static var timelineSortDirection: ComparisonResult { + var timelineSortDirection: ComparisonResult { get { - return sortDirection(for: Key.timelineSortDirection) + return AppDefaults.sortDirection(for: Key.timelineSortDirection) } set { - setSortDirection(for: Key.timelineSortDirection, newValue) + AppDefaults.setSortDirection(for: Key.timelineSortDirection, newValue) } } - static var articleFullscreenAvailable: Bool { + var articleFullscreenAvailable: Bool { get { - return bool(for: Key.articleFullscreenAvailable) + return AppDefaults.bool(for: Key.articleFullscreenAvailable) } set { - setBool(for: Key.articleFullscreenAvailable, newValue) + AppDefaults.setBool(for: Key.articleFullscreenAvailable, newValue) } } - static var articleFullscreenEnabled: Bool { + var articleFullscreenEnabled: Bool { get { - return bool(for: Key.articleFullscreenEnabled) + return AppDefaults.bool(for: Key.articleFullscreenEnabled) } set { - setBool(for: Key.articleFullscreenEnabled, newValue) + AppDefaults.setBool(for: Key.articleFullscreenEnabled, newValue) } } - static var confirmMarkAllAsRead: Bool { + var confirmMarkAllAsRead: Bool { get { - return bool(for: Key.confirmMarkAllAsRead) + return AppDefaults.bool(for: Key.confirmMarkAllAsRead) } set { - setBool(for: Key.confirmMarkAllAsRead, newValue) + AppDefaults.setBool(for: Key.confirmMarkAllAsRead, newValue) } } - static var lastRefresh: Date? { + var lastRefresh: Date? { get { - return date(for: Key.lastRefresh) + return AppDefaults.date(for: Key.lastRefresh) } set { - setDate(for: Key.lastRefresh, newValue) + AppDefaults.setDate(for: Key.lastRefresh, newValue) } } - static var timelineNumberOfLines: Int { + var timelineNumberOfLines: Int { get { - return int(for: Key.timelineNumberOfLines) + return AppDefaults.int(for: Key.timelineNumberOfLines) } set { - setInt(for: Key.timelineNumberOfLines, newValue) + AppDefaults.setInt(for: Key.timelineNumberOfLines, newValue) } } - static var timelineIconSize: IconSize { + var timelineIconSize: IconSize { get { - let rawValue = AppDefaults.shared.integer(forKey: Key.timelineIconSize) + let rawValue = AppDefaults.store.integer(forKey: Key.timelineIconSize) return IconSize(rawValue: rawValue) ?? IconSize.medium } set { - AppDefaults.shared.set(newValue.rawValue, forKey: Key.timelineIconSize) + AppDefaults.store.set(newValue.rawValue, forKey: Key.timelineIconSize) } } @@ -217,7 +220,7 @@ struct AppDefaults { Key.articleFullscreenAvailable: false, Key.articleFullscreenEnabled: false, Key.confirmMarkAllAsRead: true] - AppDefaults.shared.register(defaults: defaults) + AppDefaults.store.register(defaults: defaults) } } @@ -242,27 +245,27 @@ private extension AppDefaults { } static func bool(for key: String) -> Bool { - return AppDefaults.shared.bool(forKey: key) + return AppDefaults.store.bool(forKey: key) } static func setBool(for key: String, _ flag: Bool) { - AppDefaults.shared.set(flag, forKey: key) + AppDefaults.store.set(flag, forKey: key) } static func int(for key: String) -> Int { - return AppDefaults.shared.integer(forKey: key) + return AppDefaults.store.integer(forKey: key) } static func setInt(for key: String, _ x: Int) { - AppDefaults.shared.set(x, forKey: key) + AppDefaults.store.set(x, forKey: key) } static func date(for key: String) -> Date? { - return AppDefaults.shared.object(forKey: key) as? Date + return AppDefaults.store.object(forKey: key) as? Date } static func setDate(for key: String, _ date: Date?) { - AppDefaults.shared.set(date, forKey: key) + AppDefaults.store.set(date, forKey: key) } static func sortDirection(for key:String) -> ComparisonResult { diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index 5dfa840bb..6d99165ff 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -73,7 +73,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { AppDefaults.registerDefaults() - let isFirstRun = AppDefaults.isFirstRun + let isFirstRun = AppDefaults.shared.isFirstRun if isFirstRun { os_log("Is first run.", log: log, type: .info) } @@ -139,7 +139,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } @objc func accountRefreshDidFinish(_ note: Notification) { - AppDefaults.lastRefresh = Date() + AppDefaults.shared.lastRefresh = Date() } // MARK: - API @@ -170,7 +170,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD extensionFeedAddRequestFile.resume() syncTimer?.update() - if let lastRefresh = AppDefaults.lastRefresh { + if let lastRefresh = AppDefaults.shared.lastRefresh { if Date() > lastRefresh.addingTimeInterval(15 * 60) { AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) } else { diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index ddd32ad4a..97b4f7f8a 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -132,7 +132,7 @@ class ArticleViewController: UIViewController { articleExtractorButton.buttonState = controller.articleExtractorButtonState self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil) - if AppDefaults.articleFullscreenEnabled { + if AppDefaults.shared.articleFullscreenEnabled { controller.hideBars() } @@ -183,7 +183,7 @@ class ArticleViewController: UIViewController { starBarButtonItem.isEnabled = true let permalinkPresent = article.preferredLink != nil - articleExtractorButton.isEnabled = permalinkPresent && !AppDefaults.isDeveloperBuild + articleExtractorButton.isEnabled = permalinkPresent && !AppDefaults.shared.isDeveloperBuild actionBarButtonItem.isEnabled = permalinkPresent if article.status.read { @@ -230,7 +230,7 @@ class ArticleViewController: UIViewController { @objc func willEnterForeground(_ note: Notification) { // The toolbar will come back on you if you don't hide it again - if AppDefaults.articleFullscreenEnabled { + if AppDefaults.shared.articleFullscreenEnabled { currentWebViewController?.hideBars() } } diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift index 530443e2a..439d85d51 100644 --- a/iOS/Article/WebViewController.swift +++ b/iOS/Article/WebViewController.swift @@ -37,7 +37,7 @@ class WebViewController: UIViewController { private lazy var contextMenuInteraction = UIContextMenuInteraction(delegate: self) private var isFullScreenAvailable: Bool { - return AppDefaults.articleFullscreenAvailable && traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed + return AppDefaults.shared.articleFullscreenAvailable && traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed } private lazy var transition = ImageTransition(controller: self) private var clickedImageCompletion: (() -> Void)? @@ -155,7 +155,7 @@ class WebViewController: UIViewController { } func showBars() { - AppDefaults.articleFullscreenEnabled = false + AppDefaults.shared.articleFullscreenEnabled = false coordinator.showStatusBar() topShowBarsViewConstraint?.constant = 0 bottomShowBarsViewConstraint?.constant = 0 @@ -166,7 +166,7 @@ class WebViewController: UIViewController { func hideBars() { if isFullScreenAvailable { - AppDefaults.articleFullscreenEnabled = true + AppDefaults.shared.articleFullscreenEnabled = true coordinator.hideStatusBar() topShowBarsViewConstraint?.constant = -44.0 bottomShowBarsViewConstraint?.constant = 44.0 @@ -613,7 +613,7 @@ private extension WebViewController { topShowBarsView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(topShowBarsView) - if AppDefaults.articleFullscreenEnabled { + if AppDefaults.shared.articleFullscreenEnabled { topShowBarsViewConstraint = view.topAnchor.constraint(equalTo: topShowBarsView.bottomAnchor, constant: -44.0) } else { topShowBarsViewConstraint = view.topAnchor.constraint(equalTo: topShowBarsView.bottomAnchor, constant: 0.0) @@ -633,7 +633,7 @@ private extension WebViewController { topShowBarsView.backgroundColor = .clear bottomShowBarsView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(bottomShowBarsView) - if AppDefaults.articleFullscreenEnabled { + if AppDefaults.shared.articleFullscreenEnabled { bottomShowBarsViewConstraint = view.bottomAnchor.constraint(equalTo: bottomShowBarsView.topAnchor, constant: 44.0) } else { bottomShowBarsViewConstraint = view.bottomAnchor.constraint(equalTo: bottomShowBarsView.topAnchor, constant: 0.0) diff --git a/iOS/MasterTimeline/MarkAsReadAlertController.swift b/iOS/MasterTimeline/MarkAsReadAlertController.swift index 23e064ce5..7bc0b9b55 100644 --- a/iOS/MasterTimeline/MarkAsReadAlertController.swift +++ b/iOS/MasterTimeline/MarkAsReadAlertController.swift @@ -29,7 +29,7 @@ struct MarkAsReadAlertController { return } - if AppDefaults.confirmMarkAllAsRead { + if AppDefaults.shared.confirmMarkAllAsRead { let alertController = MarkAsReadAlertController.alert(coordinator: coordinator, confirmTitle: confirmTitle, cancelCompletion: cancelCompletion, sourceType: sourceType) { _ in completion() } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index efd541abe..b2d3eb40d 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -68,8 +68,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner // Configure the table tableView.dataSource = dataSource - numberOfTextLines = AppDefaults.timelineNumberOfLines - iconSize = AppDefaults.timelineIconSize + numberOfTextLines = AppDefaults.shared.timelineNumberOfLines + iconSize = AppDefaults.shared.timelineIconSize resetEstimatedRowHeight() if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView { @@ -455,9 +455,9 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } @objc func userDefaultsDidChange(_ note: Notification) { - if numberOfTextLines != AppDefaults.timelineNumberOfLines || iconSize != AppDefaults.timelineIconSize { - numberOfTextLines = AppDefaults.timelineNumberOfLines - iconSize = AppDefaults.timelineIconSize + if numberOfTextLines != AppDefaults.shared.timelineNumberOfLines || iconSize != AppDefaults.shared.timelineIconSize { + numberOfTextLines = AppDefaults.shared.timelineNumberOfLines + iconSize = AppDefaults.shared.timelineIconSize resetEstimatedRowHeight() reloadAllVisibleCells() } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 6c67bca5d..823fc194f 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -87,7 +87,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { var isTimelineViewControllerPending = false var isArticleViewControllerPending = false - private(set) var sortDirection = AppDefaults.timelineSortDirection { + private(set) var sortDirection = AppDefaults.shared.timelineSortDirection { didSet { if sortDirection != oldValue { sortParametersDidChange() @@ -95,7 +95,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } } - private(set) var groupByFeed = AppDefaults.timelineGroupByFeed { + private(set) var groupByFeed = AppDefaults.shared.timelineGroupByFeed { didSet { if groupByFeed != oldValue { sortParametersDidChange() @@ -546,8 +546,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } @objc func userDefaultsDidChange(_ note: Notification) { - self.sortDirection = AppDefaults.timelineSortDirection - self.groupByFeed = AppDefaults.timelineGroupByFeed + self.sortDirection = AppDefaults.shared.timelineSortDirection + self.groupByFeed = AppDefaults.shared.timelineGroupByFeed } @objc func accountDidDownloadArticles(_ note: Notification) { @@ -582,7 +582,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { if isReadFeedsFiltered { rebuildBackingStores() } - if isReadArticlesFiltered && (AppDefaults.refreshClearsReadArticles || !conditional) { + if isReadArticlesFiltered && (AppDefaults.shared.refreshClearsReadArticles || !conditional) { refreshTimeline(resetScroll: false) } } diff --git a/iOS/Settings/AddAccountViewController.swift b/iOS/Settings/AddAccountViewController.swift index 921969cbc..5ca7017b7 100644 --- a/iOS/Settings/AddAccountViewController.swift +++ b/iOS/Settings/AddAccountViewController.swift @@ -152,7 +152,7 @@ private extension AddAccountViewController { } } - if AppDefaults.isDeveloperBuild { + if AppDefaults.shared.isDeveloperBuild { removeAccountType(.cloudKit) removeAccountType(.feedly) removeAccountType(.feedWrangler) diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift index 3814e3ddc..188f80bc5 100644 --- a/iOS/Settings/SettingsViewController.swift +++ b/iOS/Settings/SettingsViewController.swift @@ -46,31 +46,31 @@ class SettingsViewController: UITableViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - if AppDefaults.timelineSortDirection == .orderedAscending { + if AppDefaults.shared.timelineSortDirection == .orderedAscending { timelineSortOrderSwitch.isOn = true } else { timelineSortOrderSwitch.isOn = false } - if AppDefaults.timelineGroupByFeed { + if AppDefaults.shared.timelineGroupByFeed { groupByFeedSwitch.isOn = true } else { groupByFeedSwitch.isOn = false } - if AppDefaults.refreshClearsReadArticles { + if AppDefaults.shared.refreshClearsReadArticles { refreshClearsReadArticlesSwitch.isOn = true } else { refreshClearsReadArticlesSwitch.isOn = false } - if AppDefaults.confirmMarkAllAsRead { + if AppDefaults.shared.confirmMarkAllAsRead { confirmMarkAllAsReadSwitch.isOn = true } else { confirmMarkAllAsReadSwitch.isOn = false } - if AppDefaults.articleFullscreenAvailable { + if AppDefaults.shared.articleFullscreenAvailable { showFullscreenArticlesSwitch.isOn = true } else { showFullscreenArticlesSwitch.isOn = false @@ -286,41 +286,41 @@ class SettingsViewController: UITableViewController { @IBAction func switchTimelineOrder(_ sender: Any) { if timelineSortOrderSwitch.isOn { - AppDefaults.timelineSortDirection = .orderedAscending + AppDefaults.shared.timelineSortDirection = .orderedAscending } else { - AppDefaults.timelineSortDirection = .orderedDescending + AppDefaults.shared.timelineSortDirection = .orderedDescending } } @IBAction func switchGroupByFeed(_ sender: Any) { if groupByFeedSwitch.isOn { - AppDefaults.timelineGroupByFeed = true + AppDefaults.shared.timelineGroupByFeed = true } else { - AppDefaults.timelineGroupByFeed = false + AppDefaults.shared.timelineGroupByFeed = false } } @IBAction func switchClearsReadArticles(_ sender: Any) { if refreshClearsReadArticlesSwitch.isOn { - AppDefaults.refreshClearsReadArticles = true + AppDefaults.shared.refreshClearsReadArticles = true } else { - AppDefaults.refreshClearsReadArticles = false + AppDefaults.shared.refreshClearsReadArticles = false } } @IBAction func switchConfirmMarkAllAsRead(_ sender: Any) { if confirmMarkAllAsReadSwitch.isOn { - AppDefaults.confirmMarkAllAsRead = true + AppDefaults.shared.confirmMarkAllAsRead = true } else { - AppDefaults.confirmMarkAllAsRead = false + AppDefaults.shared.confirmMarkAllAsRead = false } } @IBAction func switchFullscreenArticles(_ sender: Any) { if showFullscreenArticlesSwitch.isOn { - AppDefaults.articleFullscreenAvailable = true + AppDefaults.shared.articleFullscreenAvailable = true } else { - AppDefaults.articleFullscreenAvailable = false + AppDefaults.shared.articleFullscreenAvailable = false } } diff --git a/iOS/Settings/TimelineCustomizerViewController.swift b/iOS/Settings/TimelineCustomizerViewController.swift index 4a3b4af4c..f9d15bb9a 100644 --- a/iOS/Settings/TimelineCustomizerViewController.swift +++ b/iOS/Settings/TimelineCustomizerViewController.swift @@ -27,11 +27,11 @@ class TimelineCustomizerViewController: UIViewController { super.viewDidLoad() iconSizeSliderContainerView.layer.cornerRadius = 10 - iconSizeSlider.value = Float(AppDefaults.timelineIconSize.rawValue) + iconSizeSlider.value = Float(AppDefaults.shared.timelineIconSize.rawValue) iconSizeSlider.addTickMarks() numberOfLinesSliderContainerView.layer.cornerRadius = 10 - numberOfLinesSlider.value = Float(AppDefaults.timelineNumberOfLines) + numberOfLinesSlider.value = Float(AppDefaults.shared.timelineNumberOfLines) numberOfLinesSlider.addTickMarks() } @@ -48,12 +48,12 @@ class TimelineCustomizerViewController: UIViewController { @IBAction func iconSizeChanged(_ sender: Any) { guard let iconSize = IconSize(rawValue: Int(iconSizeSlider.value.rounded())) else { return } - AppDefaults.timelineIconSize = iconSize + AppDefaults.shared.timelineIconSize = iconSize updatePreview() } @IBAction func numberOfLinesChanged(_ sender: Any) { - AppDefaults.timelineNumberOfLines = Int(numberOfLinesSlider.value.rounded()) + AppDefaults.shared.timelineNumberOfLines = Int(numberOfLinesSlider.value.rounded()) updatePreview() } diff --git a/iOS/Settings/TimelinePreviewTableViewController.swift b/iOS/Settings/TimelinePreviewTableViewController.swift index 525c560de..5d6da3f0b 100644 --- a/iOS/Settings/TimelinePreviewTableViewController.swift +++ b/iOS/Settings/TimelinePreviewTableViewController.swift @@ -71,7 +71,7 @@ private extension TimelinePreviewTableViewController { let iconImage = IconImage(AppAssets.faviconTemplateImage.withTintColor(AppAssets.secondaryAccentColor)) - return MasterTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Feed Name", byline: nil, iconImage: iconImage, showIcon: true, featuredImage: nil, numberOfLines: AppDefaults.timelineNumberOfLines, iconSize: AppDefaults.timelineIconSize) + return MasterTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Feed Name", byline: nil, iconImage: iconImage, showIcon: true, featuredImage: nil, numberOfLines: AppDefaults.shared.timelineNumberOfLines, iconSize: AppDefaults.shared.timelineIconSize) } } diff --git a/iOS/ShareExtension/ShareDefaultContainer.swift b/iOS/ShareExtension/ShareDefaultContainer.swift index f039813fe..ecacd8d1e 100644 --- a/iOS/ShareExtension/ShareDefaultContainer.swift +++ b/iOS/ShareExtension/ShareDefaultContainer.swift @@ -12,8 +12,8 @@ struct ShareDefaultContainer { static func defaultContainer(containers: ExtensionContainers) -> ExtensionContainer? { - if let accountID = AppDefaults.addWebFeedAccountID, let account = containers.accounts.first(where: { $0.accountID == accountID }) { - if let folderName = AppDefaults.addWebFeedFolderName, let folder = account.folders.first(where: { $0.name == folderName }) { + if let accountID = AppDefaults.shared.addWebFeedAccountID, let account = containers.accounts.first(where: { $0.accountID == accountID }) { + if let folderName = AppDefaults.shared.addWebFeedFolderName, let folder = account.folders.first(where: { $0.name == folderName }) { return folder } else { return substituteContainerIfNeeded(account: account) @@ -27,11 +27,11 @@ struct ShareDefaultContainer { } static func saveDefaultContainer(_ container: ExtensionContainer) { - AppDefaults.addWebFeedAccountID = container.accountID + AppDefaults.shared.addWebFeedAccountID = container.accountID if let folder = container as? ExtensionFolder { - AppDefaults.addWebFeedFolderName = folder.name + AppDefaults.shared.addWebFeedFolderName = folder.name } else { - AppDefaults.addWebFeedFolderName = nil + AppDefaults.shared.addWebFeedFolderName = nil } } From a57f98e4e70d5e888eaddc1bf4a73f1352fbd1e2 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Thu, 2 Jul 2020 11:17:38 +0800 Subject: [PATCH 7/7] NetNewsWire-Mac AppDefaults is now a singleton --- Mac/AppDefaults.swift | 123 +++++++++--------- Mac/AppDelegate.swift | 26 ++-- Mac/Browser.swift | 6 +- .../AddFolder/AddFolderWindowController.swift | 4 +- .../Detail/DetailWebViewController.swift | 2 +- Mac/MainWindow/MainWindowController.swift | 10 +- .../NNW3/NNW3ImportController.swift | 2 +- ...NNW3OpenPanelAccessoryViewController.swift | 2 +- .../OPML/ExportOPMLWindowController.swift | 4 +- .../OPML/ImportOPMLWindowController.swift | 4 +- .../Sidebar/Cell/SidebarCellAppearance.swift | 2 +- .../Sidebar/SidebarViewController.swift | 2 +- .../Cell/TimelineCellAppearance.swift | 2 +- .../Timeline/Cell/TimelineTableCellView.swift | 6 +- .../TimelineContainerViewController.swift | 4 +- .../Timeline/TimelineViewController.swift | 12 +- .../Accounts/AccountsAddViewController.swift | 2 +- .../GeneralPrefencesViewController.swift | 4 +- 18 files changed, 110 insertions(+), 107 deletions(-) diff --git a/Mac/AppDefaults.swift b/Mac/AppDefaults.swift index 447d5b4cf..9d9d9f884 100644 --- a/Mac/AppDefaults.swift +++ b/Mac/AppDefaults.swift @@ -15,6 +15,9 @@ enum FontSize: Int { } struct AppDefaults { + + static var shared = AppDefaults() + private init() {} struct Key { static let firstRunDate = "firstRunDate" @@ -50,14 +53,14 @@ struct AppDefaults { private static let smallestFontSizeRawValue = FontSize.small.rawValue private static let largestFontSizeRawValue = FontSize.veryLarge.rawValue - static let isDeveloperBuild: Bool = { + let isDeveloperBuild: Bool = { if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" { return true } return false }() - static let isFirstRun: Bool = { + var isFirstRun: Bool = { if let _ = UserDefaults.standard.object(forKey: Key.firstRunDate) as? Date { return false } @@ -65,7 +68,7 @@ struct AppDefaults { return true }() - static var windowState: [AnyHashable : Any]? { + var windowState: [AnyHashable : Any]? { get { return UserDefaults.standard.object(forKey: Key.windowState) as? [AnyHashable : Any] } @@ -74,7 +77,7 @@ struct AppDefaults { } } - static var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { + var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { get { return UserDefaults.standard.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] } @@ -83,160 +86,160 @@ struct AppDefaults { } } - static var lastImageCacheFlushDate: Date? { + var lastImageCacheFlushDate: Date? { get { - return date(for: Key.lastImageCacheFlushDate) + return AppDefaults.date(for: Key.lastImageCacheFlushDate) } set { - setDate(for: Key.lastImageCacheFlushDate, newValue) + AppDefaults.setDate(for: Key.lastImageCacheFlushDate, newValue) } } - static var openInBrowserInBackground: Bool { + var openInBrowserInBackground: Bool { get { - return bool(for: Key.openInBrowserInBackground) + return AppDefaults.bool(for: Key.openInBrowserInBackground) } set { - setBool(for: Key.openInBrowserInBackground, newValue) + AppDefaults.setBool(for: Key.openInBrowserInBackground, newValue) } } - static var sidebarFontSize: FontSize { + var sidebarFontSize: FontSize { get { return fontSize(for: Key.sidebarFontSize) } set { - setFontSize(for: Key.sidebarFontSize, newValue) + AppDefaults.setFontSize(for: Key.sidebarFontSize, newValue) } } - static var timelineFontSize: FontSize { + var timelineFontSize: FontSize { get { return fontSize(for: Key.timelineFontSize) } set { - setFontSize(for: Key.timelineFontSize, newValue) + AppDefaults.setFontSize(for: Key.timelineFontSize, newValue) } } - static var detailFontSize: FontSize { + var detailFontSize: FontSize { get { return fontSize(for: Key.detailFontSize) } set { - setFontSize(for: Key.detailFontSize, newValue) + AppDefaults.setFontSize(for: Key.detailFontSize, newValue) } } - static var addWebFeedAccountID: String? { + var addWebFeedAccountID: String? { get { - return string(for: Key.addWebFeedAccountID) + return AppDefaults.string(for: Key.addWebFeedAccountID) } set { - setString(for: Key.addWebFeedAccountID, newValue) + AppDefaults.setString(for: Key.addWebFeedAccountID, newValue) } } - static var addWebFeedFolderName: String? { + var addWebFeedFolderName: String? { get { - return string(for: Key.addWebFeedFolderName) + return AppDefaults.string(for: Key.addWebFeedFolderName) } set { - setString(for: Key.addWebFeedFolderName, newValue) + AppDefaults.setString(for: Key.addWebFeedFolderName, newValue) } } - static var addFolderAccountID: String? { + var addFolderAccountID: String? { get { - return string(for: Key.addFolderAccountID) + return AppDefaults.string(for: Key.addFolderAccountID) } set { - setString(for: Key.addFolderAccountID, newValue) + AppDefaults.setString(for: Key.addFolderAccountID, newValue) } } - static var importOPMLAccountID: String? { + var importOPMLAccountID: String? { get { - return string(for: Key.importOPMLAccountID) + return AppDefaults.string(for: Key.importOPMLAccountID) } set { - setString(for: Key.importOPMLAccountID, newValue) + AppDefaults.setString(for: Key.importOPMLAccountID, newValue) } } - static var exportOPMLAccountID: String? { + var exportOPMLAccountID: String? { get { - return string(for: Key.exportOPMLAccountID) + return AppDefaults.string(for: Key.exportOPMLAccountID) } set { - setString(for: Key.exportOPMLAccountID, newValue) + AppDefaults.setString(for: Key.exportOPMLAccountID, newValue) } } - static var defaultBrowserID: String? { + var defaultBrowserID: String? { get { - return string(for: Key.defaultBrowserID) + return AppDefaults.string(for: Key.defaultBrowserID) } set { - setString(for: Key.defaultBrowserID, newValue) + AppDefaults.setString(for: Key.defaultBrowserID, newValue) } } - static var showTitleOnMainWindow: Bool { - return bool(for: Key.showTitleOnMainWindow) + var showTitleOnMainWindow: Bool { + return AppDefaults.bool(for: Key.showTitleOnMainWindow) } - static var showDebugMenu: Bool { - return bool(for: Key.showDebugMenu) + var showDebugMenu: Bool { + return AppDefaults.bool(for: Key.showDebugMenu) } - static var hideDockUnreadCount: Bool { - return bool(for: Key.hideDockUnreadCount) + var hideDockUnreadCount: Bool { + return AppDefaults.bool(for: Key.hideDockUnreadCount) } #if !MAC_APP_STORE - static var webInspectorEnabled: Bool { + var webInspectorEnabled: Bool { get { - return bool(for: Key.webInspectorEnabled) + return AppDefaults.bool(for: Key.webInspectorEnabled) } set { - setBool(for: Key.webInspectorEnabled, newValue) + AppDefaults.setBool(for: Key.webInspectorEnabled, newValue) } } - static var webInspectorStartsAttached: Bool { + var webInspectorStartsAttached: Bool { get { - return bool(for: Key.webInspectorStartsAttached) + return AppDefaults.bool(for: Key.webInspectorStartsAttached) } set { - setBool(for: Key.webInspectorStartsAttached, newValue) + AppDefaults.setBool(for: Key.webInspectorStartsAttached, newValue) } } #endif - static var timelineSortDirection: ComparisonResult { + var timelineSortDirection: ComparisonResult { get { - return sortDirection(for: Key.timelineSortDirection) + return AppDefaults.sortDirection(for: Key.timelineSortDirection) } set { - setSortDirection(for: Key.timelineSortDirection, newValue) + AppDefaults.setSortDirection(for: Key.timelineSortDirection, newValue) } } - static var timelineGroupByFeed: Bool { + var timelineGroupByFeed: Bool { get { - return bool(for: Key.timelineGroupByFeed) + return AppDefaults.bool(for: Key.timelineGroupByFeed) } set { - setBool(for: Key.timelineGroupByFeed, newValue) + AppDefaults.setBool(for: Key.timelineGroupByFeed, newValue) } } - static var timelineShowsSeparators: Bool { - return bool(for: Key.timelineShowsSeparators) + var timelineShowsSeparators: Bool { + return AppDefaults.bool(for: Key.timelineShowsSeparators) } - static var refreshInterval: RefreshInterval { + var refreshInterval: RefreshInterval { get { let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval) return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour @@ -246,7 +249,7 @@ struct AppDefaults { } } - static func registerDefaults() { + func registerDefaults() { #if DEBUG let showDebugMenu = true #else @@ -278,7 +281,7 @@ struct AppDefaults { // TODO: revisit the above when coming back to state restoration issues. } - static func actualFontSize(for fontSize: FontSize) -> CGFloat { + func actualFontSize(for fontSize: FontSize) -> CGFloat { switch fontSize { case .small: return NSFont.systemFontSize @@ -296,14 +299,14 @@ private extension AppDefaults { static var firstRunDate: Date? { get { - return date(for: Key.firstRunDate) + return AppDefaults.date(for: Key.firstRunDate) } set { - setDate(for: Key.firstRunDate, newValue) + AppDefaults.setDate(for: Key.firstRunDate, newValue) } } - static func fontSize(for key: String) -> FontSize { + func fontSize(for key: String) -> FontSize { // Punted till after 1.0. return .medium diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index e8fbaa4ce..2a4dc590a 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -191,8 +191,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } #endif - AppDefaults.registerDefaults() - let isFirstRun = AppDefaults.isFirstRun + AppDefaults.shared.registerDefaults() + let isFirstRun = AppDefaults.shared.isFirstRun if isFirstRun { logDebugMessage("Is first run.") } @@ -237,7 +237,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UNUserNotificationCenter.current().delegate = self userNotificationManager = UserNotificationManager() - if AppDefaults.showDebugMenu { + if AppDefaults.shared.showDebugMenu { refreshTimer!.update() syncTimer!.update() @@ -417,7 +417,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } #if !MAC_APP_STORE if item.action == #selector(toggleWebInspectorEnabled(_:)) { - (item as! NSMenuItem).state = AppDefaults.webInspectorEnabled ? .on : .off + (item as! NSMenuItem).state = AppDefaults.shared.webInspectorEnabled ? .on : .off } #endif return true @@ -447,7 +447,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, // MARK: - Dock Badge @objc func updateDockBadge() { - let label = unreadCount > 0 && !AppDefaults.hideDockUnreadCount ? "\(unreadCount)" : "" + let label = unreadCount > 0 && !AppDefaults.shared.hideDockUnreadCount ? "\(unreadCount)" : "" NSApplication.shared.dockTile.badgeLabel = label } @@ -624,16 +624,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, @IBAction func sortByOldestArticleOnTop(_ sender: Any?) { - AppDefaults.timelineSortDirection = .orderedAscending + AppDefaults.shared.timelineSortDirection = .orderedAscending } @IBAction func sortByNewestArticleOnTop(_ sender: Any?) { - AppDefaults.timelineSortDirection = .orderedDescending + AppDefaults.shared.timelineSortDirection = .orderedDescending } @IBAction func groupByFeedToggled(_ sender: NSMenuItem) { - AppDefaults.timelineGroupByFeed.toggle() + AppDefaults.shared.timelineGroupByFeed.toggle() } @IBAction func checkForUpdates(_ sender: Any?) { @@ -680,13 +680,13 @@ extension AppDelegate { @IBAction func toggleWebInspectorEnabled(_ sender: Any?) { #if !MAC_APP_STORE - let newValue = !AppDefaults.webInspectorEnabled - AppDefaults.webInspectorEnabled = newValue + let newValue = !AppDefaults.shared.webInspectorEnabled + AppDefaults.shared.webInspectorEnabled = newValue // 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 // accidentally reattached. - AppDefaults.webInspectorStartsAttached = false + AppDefaults.shared.webInspectorStartsAttached = false NotificationCenter.default.post(name: .WebInspectorEnabledDidChange, object: newValue) #endif } @@ -717,13 +717,13 @@ private extension AppDelegate { func updateSortMenuItems() { - let sortByNewestOnTop = AppDefaults.timelineSortDirection == .orderedDescending + let sortByNewestOnTop = AppDefaults.shared.timelineSortDirection == .orderedDescending sortByNewestArticleOnTopMenuItem.state = sortByNewestOnTop ? .on : .off sortByOldestArticleOnTopMenuItem.state = sortByNewestOnTop ? .off : .on } func updateGroupByFeedMenuItem() { - let groupByFeedEnabled = AppDefaults.timelineGroupByFeed + let groupByFeedEnabled = AppDefaults.shared.timelineGroupByFeed groupArticlesByFeedMenuItem.state = groupByFeedEnabled ? .on : .off } } diff --git a/Mac/Browser.swift b/Mac/Browser.swift index f8e4b0cea..dfcbb1571 100644 --- a/Mac/Browser.swift +++ b/Mac/Browser.swift @@ -16,7 +16,7 @@ struct Browser { /// The user-assigned default browser, or `nil` if none was assigned /// (i.e., the system default should be used). static var defaultBrowser: MacWebBrowser? { - if let bundleID = AppDefaults.defaultBrowserID, let browser = MacWebBrowser(bundleIdentifier: bundleID) { + if let bundleID = AppDefaults.shared.defaultBrowserID, let browser = MacWebBrowser(bundleIdentifier: bundleID) { return browser } @@ -31,7 +31,7 @@ struct Browser { /// - invert: Whether to invert the "open in background in browser" preference static func open(_ urlString: String, invertPreference invert: Bool = false) { // Opens according to prefs. - open(urlString, inBackground: invert ? !AppDefaults.openInBrowserInBackground : AppDefaults.openInBrowserInBackground) + open(urlString, inBackground: invert ? !AppDefaults.shared.openInBrowserInBackground : AppDefaults.shared.openInBrowserInBackground) } @@ -56,7 +56,7 @@ struct Browser { extension Browser { static var titleForOpenInBrowserInverted: String { - let openInBackgroundPref = AppDefaults.openInBrowserInBackground + let openInBackgroundPref = AppDefaults.shared.openInBrowserInBackground return openInBackgroundPref ? NSLocalizedString("Open in Browser in Foreground", comment: "Open in Browser in Foreground menu item title") : diff --git a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift index 209f03933..84053ee20 100644 --- a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift +++ b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift @@ -36,7 +36,7 @@ class AddFolderWindowController : NSWindowController { // MARK: - NSViewController override func windowDidLoad() { - let preferredAccountID = AppDefaults.addFolderAccountID + let preferredAccountID = AppDefaults.shared.addFolderAccountID accountPopupButton.removeAllItems() let menu = NSMenu() @@ -93,7 +93,7 @@ private extension AddFolderWindowController { } let account = menuItem.representedObject as! Account - AppDefaults.addFolderAccountID = account.accountID + AppDefaults.shared.addFolderAccountID = account.accountID let folderName = self.folderNameTextField.stringValue if folderName.isEmpty { diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index e77101f9d..b7eedf991 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -116,7 +116,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { waitingForFirstReload = true #if !MAC_APP_STORE - webInspectorEnabled = AppDefaults.webInspectorEnabled + webInspectorEnabled = AppDefaults.shared.webInspectorEnabled NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil) #endif diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index eab97ca16..1ffc6bf73 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -61,7 +61,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { sharingServicePickerDelegate = SharingServicePickerDelegate(self.window) - if !AppDefaults.showTitleOnMainWindow { + if !AppDefaults.shared.showTitleOnMainWindow { window?.titleVisibility = .hidden } @@ -116,12 +116,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } func saveStateToUserDefaults() { - AppDefaults.windowState = savableState() + AppDefaults.shared.windowState = savableState() window?.saveFrame(usingName: windowAutosaveName) } func restoreStateFromUserDefaults() { - if let state = AppDefaults.windowState { + if let state = AppDefaults.shared.windowState { restoreState(from: state) window?.setFrameUsingName(windowAutosaveName, force: true) } @@ -281,7 +281,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { @IBAction func openInBrowserUsingOppositeOfSettings(_ sender: Any?) { if let link = currentLink { - Browser.open(link, inBackground: !AppDefaults.openInBrowserInBackground) + Browser.open(link, inBackground: !AppDefaults.shared.openInBrowserInBackground) } } @@ -819,7 +819,7 @@ private extension MainWindowController { } func validateToggleArticleExtractor(_ item: NSValidatedUserInterfaceItem) -> Bool { - guard !AppDefaults.isDeveloperBuild else { + guard !AppDefaults.shared.isDeveloperBuild else { return false } diff --git a/Mac/MainWindow/NNW3/NNW3ImportController.swift b/Mac/MainWindow/NNW3/NNW3ImportController.swift index 9459626bf..32084490e 100644 --- a/Mac/MainWindow/NNW3/NNW3ImportController.swift +++ b/Mac/MainWindow/NNW3/NNW3ImportController.swift @@ -82,7 +82,7 @@ private extension NNW3ImportController { guard let account = accessoryViewController.selectedAccount else { return } - AppDefaults.importOPMLAccountID = account.accountID + AppDefaults.shared.importOPMLAccountID = account.accountID NNW3ImportController.importSubscriptionsPlist(subscriptionsPlistURL, into: account) } diff --git a/Mac/MainWindow/NNW3/NNW3OpenPanelAccessoryViewController.swift b/Mac/MainWindow/NNW3/NNW3OpenPanelAccessoryViewController.swift index a7a05fc8f..110377b11 100644 --- a/Mac/MainWindow/NNW3/NNW3OpenPanelAccessoryViewController.swift +++ b/Mac/MainWindow/NNW3/NNW3OpenPanelAccessoryViewController.swift @@ -39,7 +39,7 @@ final class NNW3OpenPanelAccessoryViewController: NSViewController { menuItem.representedObject = account menu.addItem(menuItem) - if account.accountID == AppDefaults.importOPMLAccountID { + if account.accountID == AppDefaults.shared.importOPMLAccountID { accountPopUpButton.select(menuItem) } } diff --git a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift index cdbda991c..4b2ce20a6 100644 --- a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift @@ -31,7 +31,7 @@ class ExportOPMLWindowController: NSWindowController { oneMenuItem.representedObject = oneAccount menu.addItem(oneMenuItem) - if oneAccount.accountID == AppDefaults.exportOPMLAccountID { + if oneAccount.accountID == AppDefaults.shared.exportOPMLAccountID { accountPopUpButton.select(oneMenuItem) } @@ -66,7 +66,7 @@ class ExportOPMLWindowController: NSWindowController { } let account = menuItem.representedObject as! Account - AppDefaults.exportOPMLAccountID = account.accountID + AppDefaults.shared.exportOPMLAccountID = account.accountID hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) exportOPML(account: account) diff --git a/Mac/MainWindow/OPML/ImportOPMLWindowController.swift b/Mac/MainWindow/OPML/ImportOPMLWindowController.swift index 252bcac43..f0737fe93 100644 --- a/Mac/MainWindow/OPML/ImportOPMLWindowController.swift +++ b/Mac/MainWindow/OPML/ImportOPMLWindowController.swift @@ -35,7 +35,7 @@ class ImportOPMLWindowController: NSWindowController { oneMenuItem.representedObject = oneAccount menu.addItem(oneMenuItem) - if oneAccount.accountID == AppDefaults.importOPMLAccountID { + if oneAccount.accountID == AppDefaults.shared.importOPMLAccountID { accountPopUpButton.select(oneMenuItem) } @@ -70,7 +70,7 @@ class ImportOPMLWindowController: NSWindowController { } let account = menuItem.representedObject as! Account - AppDefaults.importOPMLAccountID = account.accountID + AppDefaults.shared.importOPMLAccountID = account.accountID hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) importOPML(account: account) diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift index e98517c7c..a2fe6f262 100644 --- a/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift +++ b/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift @@ -17,7 +17,7 @@ struct SidebarCellAppearance: Equatable { let textFieldFont: NSFont init(fontSize: FontSize) { - self.textFieldFontSize = AppDefaults.actualFontSize(for: fontSize) + self.textFieldFontSize = AppDefaults.shared.actualFontSize(for: fontSize) self.textFieldFont = NSFont.systemFont(ofSize: textFieldFontSize) } } diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index a6afd8cca..d3f468ba4 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -57,7 +57,7 @@ protocol SidebarDelegate: class { // MARK: - NSViewController override func viewDidLoad() { - sidebarCellAppearance = SidebarCellAppearance(fontSize: AppDefaults.sidebarFontSize) + sidebarCellAppearance = SidebarCellAppearance(fontSize: AppDefaults.shared.sidebarFontSize) outlineView.dataSource = dataSource outlineView.doubleAction = #selector(doubleClickedSidebar(_:)) diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift index caf6117ba..f213471af 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift @@ -44,7 +44,7 @@ struct TimelineCellAppearance: Equatable { init(showIcon: Bool, fontSize: FontSize) { - let actualFontSize = AppDefaults.actualFontSize(for: fontSize) + let actualFontSize = AppDefaults.shared.actualFontSize(for: fontSize) let smallItemFontSize = floor(actualFontSize * 0.90) let largeItemFontSize = actualFontSize diff --git a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift index 4dabd647c..cffc819a0 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift @@ -27,7 +27,7 @@ class TimelineTableCellView: NSTableCellView { return [self.dateView, self.feedNameView, self.titleView, self.summaryView, self.textView] }() - private var showsSeparator: Bool = AppDefaults.timelineShowsSeparators { + private var showsSeparator: Bool = AppDefaults.shared.timelineShowsSeparators { didSet { separatorView.isHidden = !showsSeparator } @@ -85,7 +85,7 @@ class TimelineTableCellView: NSTableCellView { } func timelineShowsSeparatorsDefaultDidChange() { - showsSeparator = AppDefaults.timelineShowsSeparators + showsSeparator = AppDefaults.shared.timelineShowsSeparators } override func setFrameSize(_ newSize: NSSize) { @@ -209,7 +209,7 @@ private extension TimelineTableCellView { addSubviewAtInit(feedNameView, hidden: true) addSubviewAtInit(iconView, hidden: true) addSubviewAtInit(starView, hidden: true) - addSubviewAtInit(separatorView, hidden: !AppDefaults.timelineShowsSeparators) + addSubviewAtInit(separatorView, hidden: !AppDefaults.shared.timelineShowsSeparators) makeTextFieldColorsNormal() } diff --git a/Mac/MainWindow/Timeline/TimelineContainerViewController.swift b/Mac/MainWindow/Timeline/TimelineContainerViewController.swift index 44c330390..e85dbbbd6 100644 --- a/Mac/MainWindow/Timeline/TimelineContainerViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineContainerViewController.swift @@ -167,7 +167,7 @@ private extension TimelineContainerViewController { func updateViewOptionsPopUpButton() { let localizedTitle = NSLocalizedString("Sort %@", comment: "Sort") - if AppDefaults.timelineSortDirection == .orderedAscending { + if AppDefaults.shared.timelineSortDirection == .orderedAscending { newestToOldestMenuItem.state = .off oldestToNewestMenuItem.state = .on let title = NSString.localizedStringWithFormat(localizedTitle as NSString, oldestToNewestMenuItem.title) as String @@ -179,7 +179,7 @@ private extension TimelineContainerViewController { viewOptionsPopUpButton.setTitle(title) } - if AppDefaults.timelineGroupByFeed == true { + if AppDefaults.shared.timelineGroupByFeed == true { groupByFeedMenuItem.state = .on } else { groupByFeedMenuItem.state = .off diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index a283ebd41..089f4ead0 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -142,21 +142,21 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr private var didRegisterForNotifications = false static let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5, maxInterval: 2.0) - private var sortDirection = AppDefaults.timelineSortDirection { + private var sortDirection = AppDefaults.shared.timelineSortDirection { didSet { if sortDirection != oldValue { sortParametersDidChange() } } } - private var groupByFeed = AppDefaults.timelineGroupByFeed { + private var groupByFeed = AppDefaults.shared.timelineGroupByFeed { didSet { if groupByFeed != oldValue { sortParametersDidChange() } } } - private var fontSize: FontSize = AppDefaults.timelineFontSize { + private var fontSize: FontSize = AppDefaults.shared.timelineFontSize { didSet { if fontSize != oldValue { fontSizeDidChange() @@ -611,9 +611,9 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func userDefaultsDidChange(_ note: Notification) { - self.fontSize = AppDefaults.timelineFontSize - self.sortDirection = AppDefaults.timelineSortDirection - self.groupByFeed = AppDefaults.timelineGroupByFeed + self.fontSize = AppDefaults.shared.timelineFontSize + self.sortDirection = AppDefaults.shared.timelineSortDirection + self.groupByFeed = AppDefaults.shared.timelineGroupByFeed } // MARK: - Reloading Data diff --git a/Mac/Preferences/Accounts/AccountsAddViewController.swift b/Mac/Preferences/Accounts/AccountsAddViewController.swift index f2b360d1b..92d07d12e 100644 --- a/Mac/Preferences/Accounts/AccountsAddViewController.swift +++ b/Mac/Preferences/Accounts/AccountsAddViewController.swift @@ -171,7 +171,7 @@ private extension AccountsAddViewController { } } - if AppDefaults.isDeveloperBuild { + if AppDefaults.shared.isDeveloperBuild { removeAccountType(.cloudKit) removeAccountType(.feedly) removeAccountType(.feedWrangler) diff --git a/Mac/Preferences/General/GeneralPrefencesViewController.swift b/Mac/Preferences/General/GeneralPrefencesViewController.swift index 9ea450cbb..2d3c1d765 100644 --- a/Mac/Preferences/General/GeneralPrefencesViewController.swift +++ b/Mac/Preferences/General/GeneralPrefencesViewController.swift @@ -55,7 +55,7 @@ final class GeneralPreferencesViewController: NSViewController { return } let bundleID = menuItem.representedObject as? String - AppDefaults.defaultBrowserID = bundleID + AppDefaults.shared.defaultBrowserID = bundleID updateUI() } } @@ -164,7 +164,7 @@ private extension GeneralPreferencesViewController { menu.addItem(item) } - defaultBrowserPopup.selectItem(at: defaultBrowserPopup.indexOfItem(withRepresentedObject: AppDefaults.defaultBrowserID)) + defaultBrowserPopup.selectItem(at: defaultBrowserPopup.indexOfItem(withRepresentedObject: AppDefaults.shared.defaultBrowserID)) } }