From 0e0f46fa496aaf8981fae80138bff2820c3e7280 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 1 Jul 2020 21:06:40 +0800 Subject: [PATCH] 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 */,