NetNewsWire-iOS AppDefaults is now a singleton

This commit is contained in:
Stuart Breckenridge 2020-07-02 10:47:45 +08:00
parent 0233c98a1b
commit f92b219cdc
13 changed files with 105 additions and 102 deletions

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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()
}
}

View File

@ -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)

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -152,7 +152,7 @@ private extension AddAccountViewController {
}
}
if AppDefaults.isDeveloperBuild {
if AppDefaults.shared.isDeveloperBuild {
removeAccountType(.cloudKit)
removeAccountType(.feedly)
removeAccountType(.feedWrangler)

View File

@ -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
}
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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
}
}