From 31b93c5d13bd5d5c6294dc06c3ad9b05435b0581 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sun, 12 Jul 2020 14:07:52 +0800 Subject: [PATCH 01/28] small refactoring --- Mac/Base.lproj/Preferences.storyboard | 40 +++--- Multiplatform/Shared/MainApp.swift | 1 - .../Preferences/MacPreferencesModel.swift | 35 +++++ .../Preferences/MacPreferencesView.swift | 122 ++++++++---------- .../AccountsPreferencesView.swift | 0 .../AdvancedPreferencesView.swift | 0 .../GeneralPreferencesView.swift | 34 +++++ .../View/GeneralPreferencesView.swift | 33 ----- NetNewsWire.xcodeproj/project.pbxproj | 34 ++--- 9 files changed, 159 insertions(+), 140 deletions(-) create mode 100644 Multiplatform/macOS/Preferences/MacPreferencesModel.swift rename Multiplatform/macOS/Preferences/{View => Preferences Subviews}/AccountsPreferencesView.swift (100%) rename Multiplatform/macOS/Preferences/{View => Preferences Subviews}/AdvancedPreferencesView.swift (100%) create mode 100644 Multiplatform/macOS/Preferences/Preferences Subviews/GeneralPreferencesView.swift delete mode 100644 Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift diff --git a/Mac/Base.lproj/Preferences.storyboard b/Mac/Base.lproj/Preferences.storyboard index a48d6bdf8..acc9e9694 100644 --- a/Mac/Base.lproj/Preferences.storyboard +++ b/Mac/Base.lproj/Preferences.storyboard @@ -32,14 +32,14 @@ - + - + - + @@ -47,7 +47,7 @@ - + @@ -82,7 +82,7 @@ - + @@ -90,7 +90,7 @@ - + @@ -105,7 +105,7 @@ - + @@ -162,7 +162,7 @@ - + @@ -177,7 +177,7 @@ - + @@ -416,16 +416,16 @@ - + - + - + - + @@ -532,7 +532,7 @@ - + @@ -587,16 +587,16 @@ - + - + - + - + @@ -699,7 +699,7 @@ - + diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index d58a25751..9f60267f4 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -77,7 +77,6 @@ struct MainApp: App { .navigationTitle("Preferences") .environmentObject(defaults) } - .windowToolbarStyle(UnifiedWindowToolbarStyle()) #endif diff --git a/Multiplatform/macOS/Preferences/MacPreferencesModel.swift b/Multiplatform/macOS/Preferences/MacPreferencesModel.swift new file mode 100644 index 000000000..b0df1edd9 --- /dev/null +++ b/Multiplatform/macOS/Preferences/MacPreferencesModel.swift @@ -0,0 +1,35 @@ +// +// MacPreferencesModel.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 12/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation + +class MacPreferencesModel { + + enum PreferencePane: Int, CaseIterable { + case general = 0 + case accounts = 1 + case advanced = 2 + + var description: String { + switch self { + case .general: + return "General" + case .accounts: + return "Accounts" + case .advanced: + return "Advanced" + } + } + } + var currentPreferencePane: PreferencePane = PreferencePane.general + + // General Preferences + + + +} diff --git a/Multiplatform/macOS/Preferences/MacPreferencesView.swift b/Multiplatform/macOS/Preferences/MacPreferencesView.swift index 606d7eb27..94c56849a 100644 --- a/Multiplatform/macOS/Preferences/MacPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesView.swift @@ -7,83 +7,63 @@ import SwiftUI -struct MacPreferenceViewModel { - - enum PreferencePane: Int, CaseIterable { - case general = 0 - case accounts = 1 - case advanced = 2 - - var description: String { - switch self { - case .general: - return "General" - case .accounts: - return "Accounts" - case .advanced: - return "Advanced" - } - } - } - - var currentPreferencePane: PreferencePane = PreferencePane.general - -} struct MacPreferencesView: View { - - @EnvironmentObject var defaults: AppDefaults - @State private var viewModel = MacPreferenceViewModel() - - var body: some View { - VStack { - if viewModel.currentPreferencePane == .general { - AnyView(GeneralPreferencesView().environmentObject(defaults)) - } - else if viewModel.currentPreferencePane == .accounts { - AnyView(AccountsPreferencesView().environmentObject(defaults)) - } - else { - AnyView(AdvancedPreferencesView().environmentObject(defaults)) - } - } - .toolbar { - ToolbarItem { - Button(action: { - viewModel.currentPreferencePane = .general - }, label: { - Image(systemName: "checkmark.rectangle") - Text("General") - }) - } - ToolbarItem { - Button(action: { - viewModel.currentPreferencePane = .accounts - }, label: { - Image(systemName: "network") - Text("Accounts") - }) - } - ToolbarItem { - Button(action: { - viewModel.currentPreferencePane = .advanced - }, label: { - Image(systemName: "gearshape.fill") - Text("Advanced") - }) - } - } - .presentedWindowToolbarStyle(UnifiedCompactWindowToolbarStyle()) - .presentedWindowStyle(TitleBarWindowStyle()) - .navigationTitle(Text(viewModel.currentPreferencePane.description)) - } + + @EnvironmentObject var defaults: AppDefaults + @StateObject private var viewModel = MacPreferencesModel() + + var body: some View { + VStack { + switch viewModel.currentPreferencePane { + case .general: + GeneralPreferencesView() + .environmentObject(defaults) + case .accounts: + AccountsPreferencesView() + .environmentObject(defaults) + case .advanced: + AdvancedPreferencesView() + .environmentObject(defaults) + } + } + .toolbar { + ToolbarItem { + Button(action: { + viewModel.currentPreferencePane = .general + }, label: { + Image(systemName: "checkmark.rectangle") + Text("General") + }) + } + ToolbarItem { + Button(action: { + viewModel.currentPreferencePane = .accounts + }, label: { + Image(systemName: "network") + Text("Accounts") + }) + } + ToolbarItem { + Button(action: { + viewModel.currentPreferencePane = .advanced + }, label: { + Image(systemName: "gearshape.fill") + Text("Advanced") + }) + } + } + .presentedWindowToolbarStyle(UnifiedCompactWindowToolbarStyle()) + .presentedWindowStyle(TitleBarWindowStyle()) + .navigationTitle(Text(viewModel.currentPreferencePane.description)) + } } struct MacPreferencesView_Previews: PreviewProvider { - static var previews: some View { - MacPreferencesView() - } + static var previews: some View { + MacPreferencesView() + } } diff --git a/Multiplatform/macOS/Preferences/View/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preferences Subviews/AccountsPreferencesView.swift similarity index 100% rename from Multiplatform/macOS/Preferences/View/AccountsPreferencesView.swift rename to Multiplatform/macOS/Preferences/Preferences Subviews/AccountsPreferencesView.swift diff --git a/Multiplatform/macOS/Preferences/View/AdvancedPreferencesView.swift b/Multiplatform/macOS/Preferences/Preferences Subviews/AdvancedPreferencesView.swift similarity index 100% rename from Multiplatform/macOS/Preferences/View/AdvancedPreferencesView.swift rename to Multiplatform/macOS/Preferences/Preferences Subviews/AdvancedPreferencesView.swift diff --git a/Multiplatform/macOS/Preferences/Preferences Subviews/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/Preferences Subviews/GeneralPreferencesView.swift new file mode 100644 index 000000000..0eec502b0 --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preferences Subviews/GeneralPreferencesView.swift @@ -0,0 +1,34 @@ +// +// 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/View/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift deleted file mode 100644 index 909e9a682..000000000 --- a/Multiplatform/macOS/Preferences/View/GeneralPreferencesView.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// 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/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 968be9eed..3378a408f 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 1717535624BADF33004498C6 /* MacPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1717535524BADF33004498C6 /* MacPreferencesModel.swift */; }; 172199C924AB228900A31D04 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199C824AB228900A31D04 /* SettingsView.swift */; }; 172199ED24AB2E0100A31D04 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199EC24AB2E0100A31D04 /* SafariView.swift */; }; 172199F124AB716900A31D04 /* SidebarToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */; }; @@ -1778,6 +1779,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1717535524BADF33004498C6 /* MacPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPreferencesModel.swift; sourceTree = ""; }; 172199C824AB228900A31D04 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 172199EC24AB2E0100A31D04 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbarModifier.swift; sourceTree = ""; }; @@ -2503,20 +2505,21 @@ 1729528F24AA1A4F00D65E66 /* Preferences */ = { isa = PBXGroup; children = ( + 1717535524BADF33004498C6 /* MacPreferencesModel.swift */, 1729529624AA1CD000D65E66 /* MacPreferencesView.swift */, - 1729529924AA1CE100D65E66 /* View */, + 1729529924AA1CE100D65E66 /* Preferences Subviews */, ); path = Preferences; sourceTree = ""; }; - 1729529924AA1CE100D65E66 /* View */ = { + 1729529924AA1CE100D65E66 /* Preferences Subviews */ = { isa = PBXGroup; children = ( + 1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */, 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */, 1729529124AA1CAA00D65E66 /* AdvancedPreferencesView.swift */, - 1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */, ); - path = View; + path = "Preferences Subviews"; sourceTree = ""; }; 17930ED224AF10CD00A9BA52 /* Add */ = { @@ -4145,46 +4148,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 = { @@ -4194,7 +4197,7 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.HardenedRuntime = { @@ -4204,7 +4207,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -5137,6 +5140,7 @@ 51E4996C24A8762D00B667CB /* ExtractedArticle.swift in Sources */, 51E4990824A808C300B667CB /* RSHTMLMetadata+Extension.swift in Sources */, 51919FF824AB8B7700541E64 /* TimelineView.swift in Sources */, + 1717535624BADF33004498C6 /* MacPreferencesModel.swift in Sources */, 51E4992B24A8676300B667CB /* ArticleArray.swift in Sources */, 51B54AB324B5AC830014348B /* ArticleButtonState.swift in Sources */, 17D5F17224B0BC6700375168 /* SidebarToolbarModel.swift in Sources */, From c2c138218d7ccb19e08662f061b5a15703482751 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sun, 12 Jul 2020 18:53:37 +0800 Subject: [PATCH 02/28] Default RSS feeds now displayed --- .../Preferences/MacPreferencesModel.swift | 132 +++++++++++++++--- .../Preferences/MacPreferencesView.swift | 2 +- .../AccountsPreferencesView.swift | 0 .../AdvancedPreferencesView.swift | 25 ++-- .../GeneralPreferencesView.swift | 15 +- NetNewsWire.xcodeproj/project.pbxproj | 6 +- 6 files changed, 139 insertions(+), 41 deletions(-) rename Multiplatform/macOS/Preferences/{Preferences Subviews => Preference Panes}/AccountsPreferencesView.swift (100%) rename Multiplatform/macOS/Preferences/{Preferences Subviews => Preference Panes}/AdvancedPreferencesView.swift (63%) rename Multiplatform/macOS/Preferences/{Preferences Subviews => Preference Panes}/GeneralPreferencesView.swift (64%) diff --git a/Multiplatform/macOS/Preferences/MacPreferencesModel.swift b/Multiplatform/macOS/Preferences/MacPreferencesModel.swift index b0df1edd9..12ac839f6 100644 --- a/Multiplatform/macOS/Preferences/MacPreferencesModel.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesModel.swift @@ -8,28 +8,122 @@ import Foundation -class MacPreferencesModel { +enum PreferencePane: Int, CaseIterable { + case general = 0 + case accounts = 1 + case advanced = 2 - enum PreferencePane: Int, CaseIterable { - case general = 0 - case accounts = 1 - case advanced = 2 - - var description: String { - switch self { - case .general: - return "General" - case .accounts: - return "Accounts" - case .advanced: - return "Advanced" - } + var description: String { + switch self { + case .general: + return "General" + case .accounts: + return "Accounts" + case .advanced: + return "Advanced" } } - var currentPreferencePane: PreferencePane = PreferencePane.general - - // General Preferences - +} + +class MacPreferencesModel: ObservableObject { + @Published var currentPreferencePane: PreferencePane = PreferencePane.general + @Published var rssReaders = Array(RSSReaderInfo.fetchRSSReaders(nil)) + @Published var rssReaderSelection: Set = RSSReaderInfo.fetchRSSReaders(nil) } + +// MARK:- RSS Readers + +private extension MacPreferencesModel { + + func prepareRSSReaders() { + // Top item should always be: NetNewsWire (this app) + // Additional items should be sorted alphabetically. + // Any older versions of NetNewsWire should be listed as: NetNewsWire (old version) + + + + + + } + + func registerAppWithBundleID(_ bundleID: String) { + NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feed", to: bundleID) + NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feeds", to: bundleID) + objectWillChange.send() + } + +} + + +// MARK: - RSSReaderInfo + +struct RSSReaderInfo { + + let defaultRSSReaderBundleID: String? + let rssReaders: Set + static let feedURLScheme = "feed:" + + init() { + let defaultRSSReaderBundleID = NSWorkspace.shared.defaultAppBundleID(forURLScheme: RSSReaderInfo.feedURLScheme) + self.defaultRSSReaderBundleID = defaultRSSReaderBundleID + self.rssReaders = RSSReaderInfo.fetchRSSReaders(defaultRSSReaderBundleID) + } + + static func fetchRSSReaders(_ defaultRSSReaderBundleID: String?) -> Set { + let rssReaderBundleIDs = NSWorkspace.shared.bundleIDsForApps(forURLScheme: feedURLScheme) + + var rssReaders = Set() + if let defaultRSSReaderBundleID = defaultRSSReaderBundleID, let defaultReader = RSSReader(bundleID: defaultRSSReaderBundleID) { + rssReaders.insert(defaultReader) + } + rssReaderBundleIDs.forEach { (bundleID) in + if let reader = RSSReader(bundleID: bundleID) { + rssReaders.insert(reader) + } + } + return rssReaders + } +} + + +// MARK: - RSSReader + +struct RSSReader: Hashable { + + let bundleID: String + let name: String + let nameMinusAppSuffix: String + let path: String + + init?(bundleID: String) { + guard let path = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID) else { + return nil + } + + self.path = path.path + self.bundleID = bundleID + + let name = (self.path as NSString).lastPathComponent + self.name = name + if name.hasSuffix(".app") { + self.nameMinusAppSuffix = name.stripping(suffix: ".app") + } + else { + self.nameMinusAppSuffix = name + } + } + + // MARK: - Hashable + + func hash(into hasher: inout Hasher) { + hasher.combine(bundleID) + } + + // MARK: - Equatable + + static func ==(lhs: RSSReader, rhs: RSSReader) -> Bool { + return lhs.bundleID == rhs.bundleID + } +} diff --git a/Multiplatform/macOS/Preferences/MacPreferencesView.swift b/Multiplatform/macOS/Preferences/MacPreferencesView.swift index 94c56849a..ceb8bcdd3 100644 --- a/Multiplatform/macOS/Preferences/MacPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesView.swift @@ -17,7 +17,7 @@ struct MacPreferencesView: View { VStack { switch viewModel.currentPreferencePane { case .general: - GeneralPreferencesView() + GeneralPreferencesView(preferences: viewModel) .environmentObject(defaults) case .accounts: AccountsPreferencesView() diff --git a/Multiplatform/macOS/Preferences/Preferences Subviews/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift similarity index 100% rename from Multiplatform/macOS/Preferences/Preferences Subviews/AccountsPreferencesView.swift rename to Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift diff --git a/Multiplatform/macOS/Preferences/Preferences Subviews/AdvancedPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/AdvancedPreferencesView.swift similarity index 63% rename from Multiplatform/macOS/Preferences/Preferences Subviews/AdvancedPreferencesView.swift rename to Multiplatform/macOS/Preferences/Preference Panes/AdvancedPreferencesView.swift index e493f3582..800514cf0 100644 --- a/Multiplatform/macOS/Preferences/Preferences Subviews/AdvancedPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/AdvancedPreferencesView.swift @@ -12,37 +12,34 @@ struct AdvancedPreferencesView: View { @EnvironmentObject private var preferences: AppDefaults var body: some View { - VStack { + Form { Toggle("Check for app updates automatically", isOn: $preferences.checkForUpdatesAutomatically) Toggle("Download Test Builds", isOn: $preferences.downloadTestBuilds) - HStack { - Spacer() - Text("If you’re not sure, don't enable test builds. Test builds may have bugs, which may include crashing bugs and data loss.").foregroundColor(.secondary) - Spacer() - } + + Text("If you’re not sure, don't enable test builds. Test builds may have bugs, which may include crashing bugs and data loss.") + .foregroundColor(.secondary) + .lineLimit(3) + .padding(.bottom, 8) HStack { Spacer() Button("Check for Updates", action: {}) Spacer() - }.padding(.vertical, 12) + }.padding(.bottom, 8) Toggle("Send Crash Logs Automatically", isOn: $preferences.sendCrashLogs) - Spacer() HStack { Spacer() - Button("Privacy Policy", action: {}) + Button("Privacy Policy", action: { + NSWorkspace.shared.open(URL(string: "https://ranchero.com/netnewswire/privacypolicy")!) + }) Spacer() }.padding(.top, 12) - - - } - Spacer() - }.frame(width: 300, alignment: .center) + }.frame(width: 400, alignment: .center) } } diff --git a/Multiplatform/macOS/Preferences/Preferences Subviews/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift similarity index 64% rename from Multiplatform/macOS/Preferences/Preferences Subviews/GeneralPreferencesView.swift rename to Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift index 0eec502b0..e67c91f12 100644 --- a/Multiplatform/macOS/Preferences/Preferences Subviews/GeneralPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift @@ -10,11 +10,11 @@ import SwiftUI struct GeneralPreferencesView: View { @EnvironmentObject private var defaults: AppDefaults + @ObservedObject var preferences: MacPreferencesModel var body: some View { - VStack { Form { - Picker("Refresh Feeds", + Picker("Refresh feeds", selection: $defaults.interval, content: { ForEach(RefreshInterval.allCases, content: { interval in @@ -23,12 +23,19 @@ struct GeneralPreferencesView: View { }) .frame(width: 300, alignment: .center) + Picker("Default RSS reader", selection: $preferences.rssReaderSelection, content: { + ForEach(0.. Date: Sun, 12 Jul 2020 21:37:27 +0800 Subject: [PATCH 03/28] Can set default RSS reader in the General pane --- Multiplatform/macOS/Info.plist | 14 ++++++ .../Preferences/MacPreferencesModel.swift | 50 ++++++++++++------- .../GeneralPreferencesView.swift | 10 ++-- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/Multiplatform/macOS/Info.plist b/Multiplatform/macOS/Info.plist index 228514a07..0d9d0860c 100644 --- a/Multiplatform/macOS/Info.plist +++ b/Multiplatform/macOS/Info.plist @@ -35,5 +35,19 @@ $(ORGANIZATION_IDENTIFIER) DeveloperEntitlements $(DEVELOPER_ENTITLEMENTS) + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + RSS Feed + CFBundleURLSchemes + + feed + feeds + + + diff --git a/Multiplatform/macOS/Preferences/MacPreferencesModel.swift b/Multiplatform/macOS/Preferences/MacPreferencesModel.swift index 12ac839f6..765ceffaf 100644 --- a/Multiplatform/macOS/Preferences/MacPreferencesModel.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesModel.swift @@ -28,8 +28,20 @@ enum PreferencePane: Int, CaseIterable { class MacPreferencesModel: ObservableObject { @Published var currentPreferencePane: PreferencePane = PreferencePane.general - @Published var rssReaders = Array(RSSReaderInfo.fetchRSSReaders(nil)) - @Published var rssReaderSelection: Set = RSSReaderInfo.fetchRSSReaders(nil) + @Published var rssReaders = [RSSReader]() + @Published var readerSelection: Int = 0 { + willSet { + if newValue != readerSelection { + registerAppWithBundleID(rssReaders[newValue].bundleID) + } + } + } + + private let readerInfo = RSSReaderInfo() + + init() { + prepareRSSReaders() + } } @@ -38,20 +50,27 @@ class MacPreferencesModel: ObservableObject { private extension MacPreferencesModel { func prepareRSSReaders() { - // Top item should always be: NetNewsWire (this app) - // Additional items should be sorted alphabetically. - // Any older versions of NetNewsWire should be listed as: NetNewsWire (old version) - - + // Populate rssReaders + var thisApp = RSSReader(bundleID: Bundle.main.bundleIdentifier!) + thisApp?.nameMinusAppSuffix.append(" (this app—multiplatform)") + let otherRSSReaders = readerInfo.rssReaders.filter { $0.bundleID != Bundle.main.bundleIdentifier! }.sorted(by: { $0.nameMinusAppSuffix < $1.nameMinusAppSuffix }) + rssReaders.append(thisApp!) + rssReaders.append(contentsOf: otherRSSReaders) + if readerInfo.defaultRSSReaderBundleID != nil { + let defaultReader = rssReaders.filter({ $0.bundleID == readerInfo.defaultRSSReaderBundleID }) + if defaultReader.count == 1 { + let reader = defaultReader[0] + readerSelection = rssReaders.firstIndex(of: reader)! + } + } } func registerAppWithBundleID(_ bundleID: String) { NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feed", to: bundleID) NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feeds", to: bundleID) - objectWillChange.send() } } @@ -61,23 +80,20 @@ private extension MacPreferencesModel { struct RSSReaderInfo { - let defaultRSSReaderBundleID: String? + var defaultRSSReaderBundleID: String? { + NSWorkspace.shared.defaultAppBundleID(forURLScheme: RSSReaderInfo.feedURLScheme) + } let rssReaders: Set static let feedURLScheme = "feed:" init() { - let defaultRSSReaderBundleID = NSWorkspace.shared.defaultAppBundleID(forURLScheme: RSSReaderInfo.feedURLScheme) - self.defaultRSSReaderBundleID = defaultRSSReaderBundleID - self.rssReaders = RSSReaderInfo.fetchRSSReaders(defaultRSSReaderBundleID) + self.rssReaders = RSSReaderInfo.fetchRSSReaders() } - static func fetchRSSReaders(_ defaultRSSReaderBundleID: String?) -> Set { + static func fetchRSSReaders() -> Set { let rssReaderBundleIDs = NSWorkspace.shared.bundleIDsForApps(forURLScheme: feedURLScheme) var rssReaders = Set() - if let defaultRSSReaderBundleID = defaultRSSReaderBundleID, let defaultReader = RSSReader(bundleID: defaultRSSReaderBundleID) { - rssReaders.insert(defaultReader) - } rssReaderBundleIDs.forEach { (bundleID) in if let reader = RSSReader(bundleID: bundleID) { rssReaders.insert(reader) @@ -94,7 +110,7 @@ struct RSSReader: Hashable { let bundleID: String let name: String - let nameMinusAppSuffix: String + var nameMinusAppSuffix: String let path: String init?(bundleID: String) { diff --git a/Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift index e67c91f12..f20bc1095 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift @@ -18,14 +18,16 @@ struct GeneralPreferencesView: View { selection: $defaults.interval, content: { ForEach(RefreshInterval.allCases, content: { interval in - Text(interval.description()).tag(interval.rawValue) + Text(interval.description()) + .tag(interval.rawValue) }) }) .frame(width: 300, alignment: .center) - Picker("Default RSS reader", selection: $preferences.rssReaderSelection, content: { - ForEach(0.. Date: Sun, 12 Jul 2020 21:47:29 +0800 Subject: [PATCH 04/28] adds (old version) to other NNW entries --- .../GeneralPreferencesView.swift | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift index f20bc1095..8d0056150 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/GeneralPreferencesView.swift @@ -13,31 +13,37 @@ struct GeneralPreferencesView: View { @ObservedObject var preferences: MacPreferencesModel var body: some View { - 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) - - Picker("Default RSS reader", selection: $preferences.readerSelection, content: { - ForEach(0.. 0 && preferences.rssReaders[index].nameMinusAppSuffix.contains("NetNewsWire") { + Text(preferences.rssReaders[index].nameMinusAppSuffix.appending(" (old version)")) + + } else { Text(preferences.rssReaders[index].nameMinusAppSuffix) .tag(index) - }) + } + + }) - - - Toggle("Open webpages in background in browser", isOn: $defaults.openInBrowserInBackground) - - Toggle("Hide Unread Count in Dock", isOn: $defaults.hideDockUnreadCount) - } - .frame(width: 400, alignment: .center) - .lineLimit(2) + }) + + + Toggle("Open webpages in background in browser", isOn: $defaults.openInBrowserInBackground) + + Toggle("Hide Unread Count in Dock", isOn: $defaults.hideDockUnreadCount) + } + .frame(width: 400, alignment: .center) + .lineLimit(2) } } From cbeb74ab7632e0ff807538d5633bb7ddd1e14d1f Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sun, 12 Jul 2020 22:49:55 +0800 Subject: [PATCH 05/28] redesigned account pane --- .../AccountsPreferencesView.swift | 245 ++++++++++-------- 1 file changed, 143 insertions(+), 102 deletions(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift index 69be4b1d1..f7d853583 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift @@ -8,120 +8,161 @@ import SwiftUI struct AccountPreferencesViewModel { - let accountTypes = ["On My Mac", "FeedBin"] - var selectedAccount = Int?.none + let accountTypes = ["On My Mac", "FeedBin"] + var selectedAccount = Int?.none } struct AccountsPreferencesView: View { - - @State private var viewModel = AccountPreferencesViewModel() - @State private var addAccountViewModel = AccountPreferencesViewModel() - @State private var showAddAccountView: Bool = false - - var body: some View { - VStack { - HStack(alignment: .top, spacing: 10) { - VStack(alignment: .leading) { - List(selection: $viewModel.selectedAccount, content: { - ForEach(0.. Date: Mon, 13 Jul 2020 10:57:42 +0800 Subject: [PATCH 06/28] Add Account Picker now displays accounts --- .../AccountsPreferencesView.swift | 144 +++++++++++++----- 1 file changed, 103 insertions(+), 41 deletions(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift index f7d853583..60ff250ae 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift @@ -6,10 +6,17 @@ // import SwiftUI +import Account struct AccountPreferencesViewModel { - let accountTypes = ["On My Mac", "FeedBin"] - var selectedAccount = Int?.none + + // Sorted Accounts + let sortedAccounts = AccountManager.shared.sortedAccounts + + // Available Accounts + let accountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly] + + var selectedAccount: Int? = 0 } struct AccountsPreferencesView: View { @@ -24,18 +31,21 @@ struct AccountsPreferencesView: View { VStack { HStack(alignment: .top, spacing: 10) { VStack(alignment: .leading) { - List(selection: $viewModel.selectedAccount, content: { - ForEach(0.. Date: Mon, 13 Jul 2020 21:41:02 +0800 Subject: [PATCH 07/28] Adding and deleting accounts works --- .../Accounts/AccountsPreferenceModel.swift | 50 +++ .../Accounts/AccountsPreferencesView.swift | 151 +++++++++ .../Add Account/AddAccountModel.swift | 304 ++++++++++++++++++ .../Accounts/Add Account/AddAccountView.swift | 138 ++++++++ .../AccountsPreferencesView.swift | 230 ------------- .../AdvancedPreferencesView.swift | 0 .../GeneralPreferencesView.swift | 0 NetNewsWire.xcodeproj/project.pbxproj | 50 ++- 8 files changed, 690 insertions(+), 233 deletions(-) create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift delete mode 100644 Multiplatform/macOS/Preferences/Preference Panes/AccountsPreferencesView.swift rename Multiplatform/macOS/Preferences/Preference Panes/{ => Advanced}/AdvancedPreferencesView.swift (100%) rename Multiplatform/macOS/Preferences/Preference Panes/{ => General}/GeneralPreferencesView.swift (100%) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift new file mode 100644 index 000000000..79b751be3 --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift @@ -0,0 +1,50 @@ +// +// AccountsPreferenceModel.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 13/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import Account +import Combine + +class AccountsPreferenceModel: ObservableObject { + + + @Published var sortedAccounts: [Account] = [] + + // Configured Accounts + @Published var selectedConfiguredAccountID: String? = nil + + // Sheets + @Published var showAddAccountView: Bool = false + + var selectedAccountIsDefault: Bool { + guard let selected = selectedConfiguredAccountID else { + return true + } + if selected == AccountManager.shared.defaultAccount.accountID { + return true + } + return false + } + + // Subscriptions + var notifcationSubscriptions = Set() + + init() { + sortedAccounts = AccountManager.shared.sortedAccounts + + NotificationCenter.default.publisher(for: .UserDidAddAccount).sink(receiveValue: { _ in + self.sortedAccounts = AccountManager.shared.sortedAccounts + }).store(in: ¬ifcationSubscriptions) + + NotificationCenter.default.publisher(for: .UserDidDeleteAccount).sink(receiveValue: { _ in + self.selectedConfiguredAccountID = nil + self.sortedAccounts = AccountManager.shared.sortedAccounts + }).store(in: ¬ifcationSubscriptions) + } + +} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift new file mode 100644 index 000000000..26f83114e --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift @@ -0,0 +1,151 @@ +// +// AccountsPreferencesView.swift +// macOS +// +// Created by Stuart Breckenridge on 27/6/20. +// + +import SwiftUI +import Account + + +struct AccountsPreferencesView: View { + + @StateObject var viewModel = AccountsPreferenceModel() + + @State private var hoverOnAdd: Bool = false + @State private var hoverOnRemove: Bool = false + + var body: some View { + VStack { + HStack(alignment: .top, spacing: 10) { + VStack(alignment: .leading) { + List(viewModel.sortedAccounts, id: \.accountID, selection: $viewModel.selectedConfiguredAccountID) { + ConfiguredAccountRow(account: $0) + .id($0.accountID) + }.overlay( + Group { + bottomButtonStack + }, alignment: .bottom) + } + .frame(width: 225, height: 300, alignment: .leading) + .border(Color.gray, width: 1) + VStack(alignment: .leading) { + EmptyView() + Spacer() + }.frame(width: 225, height: 300, alignment: .leading) + } + Spacer() + }.sheet(isPresented: $viewModel.showAddAccountView, + onDismiss: { viewModel.showAddAccountView.toggle() }, + content: { + AddAccountView(preferencesModel: viewModel) + }) + + } + + var bottomButtonStack: some View { + VStack(alignment: .leading, spacing: 0) { + Divider() + HStack(alignment: .center, spacing: 4) { + Button(action: { + viewModel.showAddAccountView.toggle() + }, label: { + Image(systemName: "plus") + .font(.title) + .frame(width: 30, height: 30) + .overlay(RoundedRectangle(cornerRadius: 4, style: .continuous) + .foregroundColor(hoverOnAdd ? Color.gray.opacity(0.1) : Color.clear)) + .padding(4) + }) + .buttonStyle(BorderlessButtonStyle()) + .onHover { hovering in + hoverOnAdd = hovering + } + .help("Add Account") + + Button(action: { + if let account = viewModel.sortedAccounts.first(where: { $0.accountID == viewModel.selectedConfiguredAccountID }) { + AccountManager.shared.deleteAccount(account) + } + + }, label: { + Image(systemName: "minus") + .font(.title) + .frame(width: 30, height: 30) + .overlay(RoundedRectangle(cornerRadius: 4, style: .continuous) + .foregroundColor(hoverOnRemove ? Color.gray.opacity(0.1) : Color.clear)) + .padding(4) + }) + .buttonStyle(BorderlessButtonStyle()) + .onHover { hovering in + hoverOnRemove = hovering + } + .disabled(viewModel.selectedAccountIsDefault) + .help("Delete Account") + + Spacer() + } + .background(Color.white) + } + + + } + +} + +struct ConfiguredAccountRow: View { + + var account: Account + + var body: some View { + HStack(alignment: .center) { + if let img = account.smallIcon?.image { + Image(rsImage: img) + .resizable() + .frame(width: 30, height: 30) + .aspectRatio(contentMode: .fit) + } + Text(account.nameForDisplay) + }.padding(.vertical, 4) + } + +} + +struct AddAccountPickerRow: View { + + var accountType: AccountType + + var body: some View { + HStack { + if let img = AppAssets.image(for: accountType) { + Image(rsImage: img) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 15, height: 15) + } + + switch accountType { + case .onMyMac: + Text(Account.defaultLocalAccountName) + case .cloudKit: + Text("iCloud") + case .feedbin: + Text("Feedbin") + case .feedWrangler: + Text("FeedWrangler") + case .freshRSS: + Text("FreshRSS") + case .feedly: + Text("Feedly") + case .newsBlur: + Text("NewsBlur") + } + } + } +} + + + + + diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift new file mode 100644 index 000000000..8ffa3a555 --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift @@ -0,0 +1,304 @@ +// +// AddAccountModel.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 13/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import Account +import RSWeb +import Secrets + +class AddAccountModel: ObservableObject { + + enum AddAccountErrors: CustomStringConvertible { + case invalidUsernamePassword, invalidUsernamePasswordAPI, networkError, keyChainError, other(error: Error) , none + + var description: String { + switch self { + case .invalidUsernamePassword: + return NSLocalizedString("Invalid email or password combination.", comment: "Invalid email/password combination.") + case .invalidUsernamePasswordAPI: + return NSLocalizedString("Invalid email, password, or API URL combination.", comment: "Invalid email/password/API combination.") + case .networkError: + return NSLocalizedString("Network Error. Please try later.", comment: "Network Error. Please try later.") + case .keyChainError: + return NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") + case .other(let error): + return NSLocalizedString(error.localizedDescription, comment: "Other add account error") + default: + return NSLocalizedString("N/A", comment: "N/A") + } + } + + static func ==(lhs: AddAccountErrors, rhs: AddAccountErrors) -> Bool { + switch (lhs, rhs) { + case (.other(let lhsError), .other(let rhsError)): + return lhsError.localizedDescription == rhsError.localizedDescription + default: + return lhs == rhs + } + } + } + + #if DEBUG + let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly, .feedWrangler, .freshRSS, .cloudKit, .newsBlur] + #else + let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly] + #endif + + // Add Accounts + @Published var selectedAddAccount: AccountType = .onMyMac + @Published var userName: String = "" + @Published var password: String = "" + @Published var apiUrl: String = "" + @Published var newLocalAccountName: String = "" + @Published var accountIsAuthenticating: Bool = false + @Published var addAccountError: AddAccountErrors = .none { + didSet { + if addAccountError == .none { + showError = false + } else { + showError = true + } + } + } + @Published var showError: Bool = false + @Published var accountAdded: Bool = false + + func resetUserEntries() { + userName = "" + password = "" + newLocalAccountName = "" + apiUrl = "" + } + + func authenticateAccount() { + switch selectedAddAccount { + case .onMyMac: + addLocalAccount() + case .cloudKit: + authenticateCloudKit() + case .feedbin: + authenticateFeedbin() + case .feedWrangler: + authenticateFeedWrangler() + case .freshRSS: + authenticateFreshRSS() + case .feedly: + authenticateFeedly() + case .newsBlur: + authenticateNewsBlur() + } + } + +} + +// MARK:- Authentication API + +extension AddAccountModel { + + private func addLocalAccount() { + let account = AccountManager.shared.createAccount(type: .onMyMac) + account.name = newLocalAccountName + accountAdded.toggle() + } + + private func authenticateFeedbin() { + accountIsAuthenticating = true + let credentials = Credentials(type: .basic, username: userName, secret: password) + + Account.validateCredentials(type: .feedbin, credentials: credentials) { [weak self] result in + + guard let self = self else { return } + + self.accountIsAuthenticating = false + + switch result { + case .success(let validatedCredentials): + + guard let validatedCredentials = validatedCredentials else { + self.addAccountError = .invalidUsernamePassword + return + } + + let account = AccountManager.shared.createAccount(type: .feedbin) + + do { + try account.removeCredentials(type: .basic) + try account.storeCredentials(validatedCredentials) + + account.refreshAll(completion: { result in + switch result { + case .success: + self.accountAdded.toggle() + break + case .failure(let error): + self.addAccountError = .other(error: error) + } + }) + + } catch { + self.addAccountError = .keyChainError + } + + case .failure: + self.addAccountError = .networkError + } + + } + + } + + private func authenticateFeedWrangler() { + + accountIsAuthenticating = true + let credentials = Credentials(type: .feedWranglerBasic, username: userName, secret: password) + + Account.validateCredentials(type: .feedWrangler, credentials: credentials) { [weak self] result in + + guard let self = self else { return } + + self.accountIsAuthenticating = false + + switch result { + case .success(let validatedCredentials): + + guard let validatedCredentials = validatedCredentials else { + self.addAccountError = .invalidUsernamePassword + return + } + + let account = AccountManager.shared.createAccount(type: .feedWrangler) + + do { + try account.removeCredentials(type: .feedWranglerBasic) + try account.removeCredentials(type: .feedWranglerToken) + try account.storeCredentials(credentials) + try account.storeCredentials(validatedCredentials) + + account.refreshAll(completion: { result in + switch result { + case .success: + self.accountAdded.toggle() + break + case .failure(let error): + self.addAccountError = .other(error: error) + } + }) + + } catch { + self.addAccountError = .keyChainError + } + + case .failure: + self.addAccountError = .networkError + } + } + } + + private func authenticateNewsBlur() { + accountIsAuthenticating = true + let credentials = Credentials(type: .newsBlurBasic, username: userName, secret: password) + + Account.validateCredentials(type: .newsBlur, credentials: credentials) { [weak self] result in + + guard let self = self else { return } + + self.accountIsAuthenticating = false + + switch result { + case .success(let validatedCredentials): + + guard let validatedCredentials = validatedCredentials else { + self.addAccountError = .invalidUsernamePassword + return + } + + let account = AccountManager.shared.createAccount(type: .newsBlur) + + do { + try account.removeCredentials(type: .newsBlurBasic) + try account.removeCredentials(type: .newsBlurSessionId) + try account.storeCredentials(credentials) + try account.storeCredentials(validatedCredentials) + + account.refreshAll(completion: { result in + switch result { + case .success: + self.accountAdded.toggle() + break + case .failure(let error): + self.addAccountError = .other(error: error) + } + }) + + } catch { + self.addAccountError = .keyChainError + } + + case .failure: + self.addAccountError = .networkError + } + } + + } + + private func authenticateFreshRSS() { + accountIsAuthenticating = true + let credentials = Credentials(type: .readerBasic, username: userName, secret: password) + + Account.validateCredentials(type: .freshRSS, credentials: credentials, endpoint: URL(string: apiUrl)!) { [weak self] result in + + guard let self = self else { return } + + self.accountIsAuthenticating = false + + switch result { + case .success(let validatedCredentials): + + guard let validatedCredentials = validatedCredentials else { + self.addAccountError = .invalidUsernamePassword + return + } + + let account = AccountManager.shared.createAccount(type: .newsBlur) + + do { + try account.removeCredentials(type: .readerBasic) + try account.removeCredentials(type: .readerAPIKey) + try account.storeCredentials(credentials) + try account.storeCredentials(validatedCredentials) + + account.refreshAll(completion: { result in + switch result { + case .success: + self.accountAdded.toggle() + break + case .failure(let error): + self.addAccountError = .other(error: error) + } + }) + + } catch { + self.addAccountError = .keyChainError + } + + case .failure: + self.addAccountError = .networkError + } + } + } + + private func authenticateCloudKit() { + let _ = AccountManager.shared.createAccount(type: .cloudKit) + self.accountAdded.toggle() + } + + private func authenticateFeedly() { + // TBC + } + +} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift new file mode 100644 index 000000000..22ab4b694 --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift @@ -0,0 +1,138 @@ +// +// AddAccountView.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 13/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account + +struct AddAccountView: View { + + @Environment(\.presentationMode) private var presentationMode + @ObservedObject var preferencesModel: AccountsPreferenceModel + @StateObject private var viewModel = AddAccountModel() + + var body: some View { + + VStack(alignment: .leading) { + Text("Add an Account").font(.headline) + Form { + Picker("Account Type", + selection: $viewModel.selectedAddAccount, + content: { + ForEach(0.. Date: Mon, 13 Jul 2020 21:43:41 +0800 Subject: [PATCH 08/28] Updates to add account model --- .../Accounts/Add Account/AddAccountModel.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift index 8ffa3a555..5c718db4a 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift @@ -96,14 +96,14 @@ class AddAccountModel: ObservableObject { } -// MARK:- Authentication API +// MARK:- Authentication APIs extension AddAccountModel { private func addLocalAccount() { let account = AccountManager.shared.createAccount(type: .onMyMac) account.name = newLocalAccountName - accountAdded.toggle() + accountAdded = true } private func authenticateFeedbin() { @@ -133,7 +133,7 @@ extension AddAccountModel { account.refreshAll(completion: { result in switch result { case .success: - self.accountAdded.toggle() + self.accountAdded = true break case .failure(let error): self.addAccountError = .other(error: error) @@ -182,7 +182,7 @@ extension AddAccountModel { account.refreshAll(completion: { result in switch result { case .success: - self.accountAdded.toggle() + self.accountAdded = true break case .failure(let error): self.addAccountError = .other(error: error) @@ -228,7 +228,7 @@ extension AddAccountModel { account.refreshAll(completion: { result in switch result { case .success: - self.accountAdded.toggle() + self.accountAdded = true break case .failure(let error): self.addAccountError = .other(error: error) @@ -275,7 +275,7 @@ extension AddAccountModel { account.refreshAll(completion: { result in switch result { case .success: - self.accountAdded.toggle() + self.accountAdded = true break case .failure(let error): self.addAccountError = .other(error: error) @@ -294,7 +294,7 @@ extension AddAccountModel { private func authenticateCloudKit() { let _ = AccountManager.shared.createAccount(type: .cloudKit) - self.accountAdded.toggle() + self.accountAdded = true } private func authenticateFeedly() { From f4d776688b0574f75c1b5a99684512d8f682cc7a Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Mon, 13 Jul 2020 22:29:33 +0800 Subject: [PATCH 09/28] Toolbar is in a much better shape. --- .../Preferences/MacPreferencesView.swift | 60 +++++++++++-------- .../Accounts/AccountsPreferenceModel.swift | 10 +--- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/Multiplatform/macOS/Preferences/MacPreferencesView.swift b/Multiplatform/macOS/Preferences/MacPreferencesView.swift index ceb8bcdd3..0e1ce4b15 100644 --- a/Multiplatform/macOS/Preferences/MacPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesView.swift @@ -29,33 +29,43 @@ struct MacPreferencesView: View { } .toolbar { ToolbarItem { - Button(action: { - viewModel.currentPreferencePane = .general - }, label: { - Image(systemName: "checkmark.rectangle") - Text("General") - }) - } - ToolbarItem { - Button(action: { - viewModel.currentPreferencePane = .accounts - }, label: { - Image(systemName: "network") - Text("Accounts") - }) - } - ToolbarItem { - Button(action: { - viewModel.currentPreferencePane = .advanced - }, label: { - Image(systemName: "gearshape.fill") - Text("Advanced") - }) + HStack { + Button(action: { + viewModel.currentPreferencePane = .general + }, label: { + VStack { + Image(systemName: "gearshape") + .font(.title2) + Text("General") + }.foregroundColor( + viewModel.currentPreferencePane == .general ? Color("AccentColor") : Color.gray + ) + }) + Button(action: { + viewModel.currentPreferencePane = .accounts + }, label: { + VStack { + Image(systemName: "at") + .font(.title2) + Text("Accounts") + }.foregroundColor( + viewModel.currentPreferencePane == .accounts ? Color("AccentColor") : Color.gray + ) + }) + Button(action: { + viewModel.currentPreferencePane = .advanced + }, label: { + VStack { + Image(systemName: "scale.3d") + .font(.title2) + Text("Advanced") + }.foregroundColor( + viewModel.currentPreferencePane == .advanced ? Color("AccentColor") : Color.gray + ) + }) + } } } - .presentedWindowToolbarStyle(UnifiedCompactWindowToolbarStyle()) - .presentedWindowStyle(TitleBarWindowStyle()) - .navigationTitle(Text(viewModel.currentPreferencePane.description)) } } diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift index 79b751be3..31167cb53 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift @@ -14,11 +14,7 @@ class AccountsPreferenceModel: ObservableObject { @Published var sortedAccounts: [Account] = [] - - // Configured Accounts @Published var selectedConfiguredAccountID: String? = nil - - // Sheets @Published var showAddAccountView: Bool = false var selectedAccountIsDefault: Bool { @@ -32,19 +28,19 @@ class AccountsPreferenceModel: ObservableObject { } // Subscriptions - var notifcationSubscriptions = Set() + var notificationSubscriptions = Set() init() { sortedAccounts = AccountManager.shared.sortedAccounts NotificationCenter.default.publisher(for: .UserDidAddAccount).sink(receiveValue: { _ in self.sortedAccounts = AccountManager.shared.sortedAccounts - }).store(in: ¬ifcationSubscriptions) + }).store(in: ¬ificationSubscriptions) NotificationCenter.default.publisher(for: .UserDidDeleteAccount).sink(receiveValue: { _ in self.selectedConfiguredAccountID = nil self.sortedAccounts = AccountManager.shared.sortedAccounts - }).store(in: ¬ifcationSubscriptions) + }).store(in: ¬ificationSubscriptions) } } From 8a66c368cd05d52825317ad4e561bf449985cac3 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Mon, 13 Jul 2020 22:51:09 +0800 Subject: [PATCH 10/28] fixes enum ==, changes frames --- Multiplatform/macOS/Preferences/MacPreferencesView.swift | 6 +++--- .../Preference Panes/Accounts/AccountsPreferencesView.swift | 2 +- .../Accounts/Add Account/AddAccountModel.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Multiplatform/macOS/Preferences/MacPreferencesView.swift b/Multiplatform/macOS/Preferences/MacPreferencesView.swift index 0e1ce4b15..e663647ad 100644 --- a/Multiplatform/macOS/Preferences/MacPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesView.swift @@ -40,7 +40,7 @@ struct MacPreferencesView: View { }.foregroundColor( viewModel.currentPreferencePane == .general ? Color("AccentColor") : Color.gray ) - }) + }).frame(width: 70) Button(action: { viewModel.currentPreferencePane = .accounts }, label: { @@ -51,7 +51,7 @@ struct MacPreferencesView: View { }.foregroundColor( viewModel.currentPreferencePane == .accounts ? Color("AccentColor") : Color.gray ) - }) + }).frame(width: 70) Button(action: { viewModel.currentPreferencePane = .advanced }, label: { @@ -62,7 +62,7 @@ struct MacPreferencesView: View { }.foregroundColor( viewModel.currentPreferencePane == .advanced ? Color("AccentColor") : Color.gray ) - }) + }).frame(width: 70) } } } diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift index 26f83114e..fe9284bcb 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift @@ -103,7 +103,7 @@ struct ConfiguredAccountRow: View { if let img = account.smallIcon?.image { Image(rsImage: img) .resizable() - .frame(width: 30, height: 30) + .frame(width: 20, height: 20) .aspectRatio(contentMode: .fit) } Text(account.nameForDisplay) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift index 5c718db4a..515da26e0 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift @@ -38,7 +38,7 @@ class AddAccountModel: ObservableObject { case (.other(let lhsError), .other(let rhsError)): return lhsError.localizedDescription == rhsError.localizedDescription default: - return lhs == rhs + return false } } } From 587acfcb1cec404391d562d566a857aaa9bcac17 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Mon, 13 Jul 2020 23:02:09 +0800 Subject: [PATCH 11/28] correctly places accountAdded call --- .../Accounts/Add Account/AddAccountModel.swift | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift index 515da26e0..b2a5a7a10 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift @@ -129,11 +129,10 @@ extension AddAccountModel { do { try account.removeCredentials(type: .basic) try account.storeCredentials(validatedCredentials) - + self.accountAdded = true account.refreshAll(completion: { result in switch result { case .success: - self.accountAdded = true break case .failure(let error): self.addAccountError = .other(error: error) @@ -178,11 +177,10 @@ extension AddAccountModel { try account.removeCredentials(type: .feedWranglerToken) try account.storeCredentials(credentials) try account.storeCredentials(validatedCredentials) - + self.accountAdded = true account.refreshAll(completion: { result in switch result { case .success: - self.accountAdded = true break case .failure(let error): self.addAccountError = .other(error: error) @@ -224,11 +222,10 @@ extension AddAccountModel { try account.removeCredentials(type: .newsBlurSessionId) try account.storeCredentials(credentials) try account.storeCredentials(validatedCredentials) - + self.accountAdded = true account.refreshAll(completion: { result in switch result { case .success: - self.accountAdded = true break case .failure(let error): self.addAccountError = .other(error: error) @@ -271,11 +268,10 @@ extension AddAccountModel { try account.removeCredentials(type: .readerAPIKey) try account.storeCredentials(credentials) try account.storeCredentials(validatedCredentials) - + self.accountAdded = true account.refreshAll(completion: { result in switch result { case .success: - self.accountAdded = true break case .failure(let error): self.addAccountError = .other(error: error) From 01c1df0a6be1981f0bcedc9d284c660eb7826040 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Mon, 13 Jul 2020 23:10:50 +0800 Subject: [PATCH 12/28] refactoring --- ...l.swift => AccountsPreferencesModel.swift} | 5 +- .../Accounts/AccountsPreferencesView.swift | 61 +------------------ .../Add Account/AddAccountPickerRow.swift | 49 +++++++++++++++ .../Accounts/Add Account/AddAccountView.swift | 4 +- .../Accounts/ConfiguredAccountRow.swift | 34 +++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 16 +++-- 6 files changed, 100 insertions(+), 69 deletions(-) rename Multiplatform/macOS/Preferences/Preference Panes/Accounts/{AccountsPreferenceModel.swift => AccountsPreferencesModel.swift} (93%) create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountPickerRow.swift create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/ConfiguredAccountRow.swift diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift similarity index 93% rename from Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift rename to Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift index 31167cb53..e680d476b 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferenceModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift @@ -1,5 +1,5 @@ // -// AccountsPreferenceModel.swift +// AccountsPreferencesModel.swift // Multiplatform macOS // // Created by Stuart Breckenridge on 13/7/20. @@ -10,8 +10,7 @@ import Foundation import Account import Combine -class AccountsPreferenceModel: ObservableObject { - +class AccountsPreferencesModel: ObservableObject { @Published var sortedAccounts: [Account] = [] @Published var selectedConfiguredAccountID: String? = nil diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift index fe9284bcb..19eeca92a 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift @@ -11,8 +11,7 @@ import Account struct AccountsPreferencesView: View { - @StateObject var viewModel = AccountsPreferenceModel() - + @StateObject var viewModel = AccountsPreferencesModel() @State private var hoverOnAdd: Bool = false @State private var hoverOnRemove: Bool = false @@ -88,64 +87,6 @@ struct AccountsPreferencesView: View { } .background(Color.white) } - - } } - -struct ConfiguredAccountRow: View { - - var account: Account - - var body: some View { - HStack(alignment: .center) { - if let img = account.smallIcon?.image { - Image(rsImage: img) - .resizable() - .frame(width: 20, height: 20) - .aspectRatio(contentMode: .fit) - } - Text(account.nameForDisplay) - }.padding(.vertical, 4) - } - -} - -struct AddAccountPickerRow: View { - - var accountType: AccountType - - var body: some View { - HStack { - if let img = AppAssets.image(for: accountType) { - Image(rsImage: img) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 15, height: 15) - } - - switch accountType { - case .onMyMac: - Text(Account.defaultLocalAccountName) - case .cloudKit: - Text("iCloud") - case .feedbin: - Text("Feedbin") - case .feedWrangler: - Text("FeedWrangler") - case .freshRSS: - Text("FreshRSS") - case .feedly: - Text("Feedly") - case .newsBlur: - Text("NewsBlur") - } - } - } -} - - - - - diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountPickerRow.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountPickerRow.swift new file mode 100644 index 000000000..3a3f29153 --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountPickerRow.swift @@ -0,0 +1,49 @@ +// +// AddAccountPickerRow.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 13/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account + +struct AddAccountPickerRow: View { + + var accountType: AccountType + + var body: some View { + HStack { + if let img = AppAssets.image(for: accountType) { + Image(rsImage: img) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 15, height: 15) + } + + switch accountType { + case .onMyMac: + Text(Account.defaultLocalAccountName) + case .cloudKit: + Text("iCloud") + case .feedbin: + Text("Feedbin") + case .feedWrangler: + Text("FeedWrangler") + case .freshRSS: + Text("FreshRSS") + case .feedly: + Text("Feedly") + case .newsBlur: + Text("NewsBlur") + } + } + } +} + +struct AddAccountPickerRow_Previews: PreviewProvider { + static var previews: some View { + AddAccountPickerRow(accountType: .onMyMac) + } +} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift index 22ab4b694..8be44fee2 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift @@ -12,7 +12,7 @@ import Account struct AddAccountView: View { @Environment(\.presentationMode) private var presentationMode - @ObservedObject var preferencesModel: AccountsPreferenceModel + @ObservedObject var preferencesModel: AccountsPreferencesModel @StateObject private var viewModel = AddAccountModel() var body: some View { @@ -133,6 +133,6 @@ struct AddAccountView: View { } struct AddAccountView_Previews: PreviewProvider { static var previews: some View { - AddAccountView(preferencesModel: AccountsPreferenceModel()) + AddAccountView(preferencesModel: AccountsPreferencesModel()) } } diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/ConfiguredAccountRow.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/ConfiguredAccountRow.swift new file mode 100644 index 000000000..7910ba6cf --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/ConfiguredAccountRow.swift @@ -0,0 +1,34 @@ +// +// ConfiguredAccountRow.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 13/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account + +struct ConfiguredAccountRow: View { + + var account: Account + + var body: some View { + HStack(alignment: .center) { + if let img = account.smallIcon?.image { + Image(rsImage: img) + .resizable() + .frame(width: 20, height: 20) + .aspectRatio(contentMode: .fit) + } + Text(account.nameForDisplay) + }.padding(.vertical, 4) + } + +} + +struct ConfiguredAccountRow_Previews: PreviewProvider { + static var previews: some View { + ConfiguredAccountRow() + } +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 6eb2a9bcc..8499a03f0 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -18,9 +18,11 @@ 1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529A24AA1FD200D65E66 /* MacSearchField.swift */; }; 175942AA24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 175942AB24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; - 1769E32224BC5925000E1E8E /* AccountsPreferenceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32124BC5925000E1E8E /* AccountsPreferenceModel.swift */; }; + 1769E32224BC5925000E1E8E /* AccountsPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */; }; 1769E32524BC5A65000E1E8E /* AddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32424BC5A65000E1E8E /* AddAccountView.swift */; }; 1769E32724BC5B6C000E1E8E /* AddAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */; }; + 1769E32924BCAFC7000E1E8E /* AddAccountPickerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */; }; + 1769E32B24BCB030000E1E8E /* ConfiguredAccountRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */; }; 1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; }; @@ -1791,9 +1793,11 @@ 1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesView.swift; sourceTree = ""; }; 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 = ""; }; - 1769E32124BC5925000E1E8E /* AccountsPreferenceModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsPreferenceModel.swift; sourceTree = ""; }; + 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsPreferencesModel.swift; sourceTree = ""; }; 1769E32424BC5A65000E1E8E /* AddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountView.swift; sourceTree = ""; }; 1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountModel.swift; sourceTree = ""; }; + 1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountPickerRow.swift; sourceTree = ""; }; + 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfiguredAccountRow.swift; sourceTree = ""; }; 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.swift; sourceTree = ""; }; 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = ""; }; @@ -2539,8 +2543,9 @@ 1769E31F24BC58A4000E1E8E /* Accounts */ = { isa = PBXGroup; children = ( - 1769E32124BC5925000E1E8E /* AccountsPreferenceModel.swift */, + 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */, 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */, + 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */, 1769E32324BC5A50000E1E8E /* Add Account */, ); path = Accounts; @@ -2559,6 +2564,7 @@ children = ( 1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */, 1769E32424BC5A65000E1E8E /* AddAccountView.swift */, + 1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */, ); path = "Add Account"; sourceTree = ""; @@ -5199,7 +5205,8 @@ 5177470A24B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */, 51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */, 5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */, - 1769E32224BC5925000E1E8E /* AccountsPreferenceModel.swift in Sources */, + 1769E32924BCAFC7000E1E8E /* AddAccountPickerRow.swift in Sources */, + 1769E32224BC5925000E1E8E /* AccountsPreferencesModel.swift in Sources */, 51E4991624A8090300B667CB /* ArticleUtilities.swift in Sources */, 51919FF224AB864A00541E64 /* TimelineModel.swift in Sources */, 51E4991A24A8090F00B667CB /* IconImage.swift in Sources */, @@ -5216,6 +5223,7 @@ 51E498C924A8085D00B667CB /* PseudoFeed.swift in Sources */, 51E498FC24A808BA00B667CB /* FaviconURLFinder.swift in Sources */, 51E4991C24A8092000B667CB /* NSAttributedString+NetNewsWire.swift in Sources */, + 1769E32B24BCB030000E1E8E /* ConfiguredAccountRow.swift in Sources */, FF64D0E824AF53EE0084080A /* RefreshProgressModel.swift in Sources */, 51E499D924A912C200B667CB /* SceneModel.swift in Sources */, 51919FB424AAB97900541E64 /* FeedIconImageLoader.swift in Sources */, From 15b980cabfeb4802d694aa8f1c280783ff56bfce Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Mon, 13 Jul 2020 23:12:08 +0800 Subject: [PATCH 13/28] removed preview --- .../Preference Panes/Accounts/ConfiguredAccountRow.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/ConfiguredAccountRow.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/ConfiguredAccountRow.swift index 7910ba6cf..baee404ba 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/ConfiguredAccountRow.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/ConfiguredAccountRow.swift @@ -27,8 +27,3 @@ struct ConfiguredAccountRow: View { } -struct ConfiguredAccountRow_Previews: PreviewProvider { - static var previews: some View { - ConfiguredAccountRow() - } -} From 39a71077b4100606a5c39e38fa0cd430952936f1 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 14 Jul 2020 10:44:59 +0800 Subject: [PATCH 14/28] WiP on Edit Account --- .../Accounts/AccountsPreferencesModel.swift | 36 ++++++++- .../Accounts/AccountsPreferencesView.swift | 9 +-- .../Accounts/EditAccountView.swift | 79 +++++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 4 + 4 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift index e680d476b..8fdf7ecbb 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift @@ -12,10 +12,32 @@ import Combine class AccountsPreferencesModel: ObservableObject { + // Configured Accounts @Published var sortedAccounts: [Account] = [] - @Published var selectedConfiguredAccountID: String? = nil + @Published var selectedConfiguredAccountID: String? = AccountManager.shared.defaultAccount.accountID { + didSet { + if let accountID = selectedConfiguredAccountID { + account = sortedAccounts.first(where: { $0.accountID == accountID }) + accountIsActive = account?.isActive ?? false + accountName = account?.name ?? "" + } + } + } @Published var showAddAccountView: Bool = false + // Edit Account + public private(set) var account: Account? + @Published var accountIsActive: Bool = false { + didSet { + account?.isActive = accountIsActive + } + } + @Published var accountName: String = "" { + didSet { + account?.name = accountName + } + } + var selectedAccountIsDefault: Bool { guard let selected = selectedConfiguredAccountID else { return true @@ -39,6 +61,18 @@ class AccountsPreferencesModel: ObservableObject { NotificationCenter.default.publisher(for: .UserDidDeleteAccount).sink(receiveValue: { _ in self.selectedConfiguredAccountID = nil self.sortedAccounts = AccountManager.shared.sortedAccounts + self.selectedConfiguredAccountID = AccountManager.shared.defaultAccount.accountID + }).store(in: ¬ificationSubscriptions) + + NotificationCenter.default.publisher(for: .AccountStateDidChange).sink(receiveValue: { notification in + guard let account = notification.object as? Account else { + return + } + if account.accountID == self.account?.accountID { + self.account = account + self.accountIsActive = account.isActive + self.accountName = account.name ?? "" + } }).store(in: ¬ificationSubscriptions) } diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift index 19eeca92a..858efc679 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift @@ -27,12 +27,11 @@ struct AccountsPreferencesView: View { bottomButtonStack }, alignment: .bottom) } - .frame(width: 225, height: 300, alignment: .leading) + .frame(width: 160, height: 300, alignment: .leading) .border(Color.gray, width: 1) - VStack(alignment: .leading) { - EmptyView() - Spacer() - }.frame(width: 225, height: 300, alignment: .leading) + + EditAccountView(viewModel: viewModel) + .frame(height: 300, alignment: .leading) } Spacer() }.sheet(isPresented: $viewModel.showAddAccountView, diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift new file mode 100644 index 000000000..e45650de0 --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift @@ -0,0 +1,79 @@ +// +// EditAccountView.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 14/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account +import Combine + +struct EditAccountView: View { + + @ObservedObject var viewModel: AccountsPreferencesModel + + var body: some View { + + ZStack { + RoundedRectangle(cornerRadius: 8, style: .circular) + .foregroundColor(Color.secondary.opacity(0.1)) + + VStack { + HStack { + Spacer() + Button("Account Information", action: {}) + Spacer() + }.padding(4) + + if viewModel.account != nil { + Form(content: { + HStack(alignment: .top) { + Text("Type: ") + .frame(width: 50) + VStack(alignment: .leading) { + Text(viewModel.account!.defaultName) + Toggle("Active", isOn: $viewModel.accountIsActive) + } + } + + HStack(alignment: .top) { + Text("Name: ") + .frame(width: 50) + VStack(alignment: .leading) { + TextField(viewModel.account!.name ?? "", text: $viewModel.accountName) + .textFieldStyle(RoundedBorderTextFieldStyle()) + Text("The name appears in the sidebar. It can be anything you want. You can even use emoji. 🎸") + .foregroundColor(.secondary) + } + } + Spacer() + if viewModel.account?.type != .onMyMac { + HStack { + Spacer() + Button("Credentials", action: { + + }) + Spacer() + } + } + + + + }).padding() + } + + Spacer() + } + } + } + + +} + +struct EditAccountView_Previews: PreviewProvider { + static var previews: some View { + EditAccountView(viewModel: AccountsPreferencesModel()) + } +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 8499a03f0..86e7de53d 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 1769E32724BC5B6C000E1E8E /* AddAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */; }; 1769E32924BCAFC7000E1E8E /* AddAccountPickerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */; }; 1769E32B24BCB030000E1E8E /* ConfiguredAccountRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */; }; + 1769E32D24BD20A0000E1E8E /* EditAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32C24BD20A0000E1E8E /* EditAccountView.swift */; }; 1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; }; @@ -1798,6 +1799,7 @@ 1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountModel.swift; sourceTree = ""; }; 1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountPickerRow.swift; sourceTree = ""; }; 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfiguredAccountRow.swift; sourceTree = ""; }; + 1769E32C24BD20A0000E1E8E /* EditAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountView.swift; sourceTree = ""; }; 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.swift; sourceTree = ""; }; 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = ""; }; @@ -2546,6 +2548,7 @@ 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */, 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */, 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */, + 1769E32C24BD20A0000E1E8E /* EditAccountView.swift */, 1769E32324BC5A50000E1E8E /* Add Account */, ); path = Accounts; @@ -5219,6 +5222,7 @@ 1729529724AA1CD000D65E66 /* MacPreferencesView.swift in Sources */, 51E4994C24A8734C00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, 1729529324AA1CAA00D65E66 /* AccountsPreferencesView.swift in Sources */, + 1769E32D24BD20A0000E1E8E /* EditAccountView.swift in Sources */, 51919FAD24AA8CCA00541E64 /* UnreadCountView.swift in Sources */, 51E498C924A8085D00B667CB /* PseudoFeed.swift in Sources */, 51E498FC24A808BA00B667CB /* FaviconURLFinder.swift in Sources */, From 64848a9421bee170d698d7aa042b4e460bc6a1ae Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 14 Jul 2020 11:33:13 +0800 Subject: [PATCH 15/28] look and feel of edit account matches production --- .../Accounts/EditAccountView.swift | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift index e45650de0..6999f22b0 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift @@ -19,49 +19,13 @@ struct EditAccountView: View { ZStack { RoundedRectangle(cornerRadius: 8, style: .circular) .foregroundColor(Color.secondary.opacity(0.1)) + .padding(.top, 8) VStack { - HStack { - Spacer() - Button("Account Information", action: {}) - Spacer() - }.padding(4) - + editAccountHeader + if viewModel.account != nil { - Form(content: { - HStack(alignment: .top) { - Text("Type: ") - .frame(width: 50) - VStack(alignment: .leading) { - Text(viewModel.account!.defaultName) - Toggle("Active", isOn: $viewModel.accountIsActive) - } - } - - HStack(alignment: .top) { - Text("Name: ") - .frame(width: 50) - VStack(alignment: .leading) { - TextField(viewModel.account!.name ?? "", text: $viewModel.accountName) - .textFieldStyle(RoundedBorderTextFieldStyle()) - Text("The name appears in the sidebar. It can be anything you want. You can even use emoji. 🎸") - .foregroundColor(.secondary) - } - } - Spacer() - if viewModel.account?.type != .onMyMac { - HStack { - Spacer() - Button("Credentials", action: { - - }) - Spacer() - } - } - - - - }).padding() + editAccountForm } Spacer() @@ -69,6 +33,47 @@ struct EditAccountView: View { } } + var editAccountHeader: some View { + HStack { + Spacer() + Button("Account Information", action: {}) + Spacer() + } + .padding([.leading, .trailing, .bottom], 4) + } + + var editAccountForm: some View { + Form(content: { + HStack(alignment: .top) { + Text("Type: ") + .frame(width: 50) + VStack(alignment: .leading) { + Text(viewModel.account!.defaultName) + Toggle("Active", isOn: $viewModel.accountIsActive) + } + } + HStack(alignment: .top) { + Text("Name: ") + .frame(width: 50) + VStack(alignment: .leading) { + TextField(viewModel.account!.name ?? "", text: $viewModel.accountName) + .textFieldStyle(RoundedBorderTextFieldStyle()) + Text("The name appears in the sidebar. It can be anything you want. You can even use emoji. 🎸") + .foregroundColor(.secondary) + } + } + Spacer() + if viewModel.account?.type != .onMyMac { + HStack { + Spacer() + Button("Credentials", action: { + + }) + Spacer() + } + } + }).padding() + } } From 4982211e276431b5bad0b1693e7c09eac01aab38 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 14 Jul 2020 14:57:55 +0800 Subject: [PATCH 16/28] WiP on Edit Accounts and ColorScheme --- Multiplatform/Shared/AppDefaults.swift | 11 +++ Multiplatform/Shared/MainApp.swift | 3 + .../Preferences/MacPreferencesView.swift | 2 + .../Accounts/AccountsPreferencesModel.swift | 32 ++++++--- .../Accounts/AccountsPreferencesView.swift | 58 +++++++++++----- .../Edit Account/EditAccountCredentials.swift | 69 +++++++++++++++++++ .../{ => Edit Account}/EditAccountView.swift | 9 ++- .../General/GeneralPreferencesView.swift | 14 +++- NetNewsWire.xcodeproj/project.pbxproj | 14 +++- 9 files changed, 177 insertions(+), 35 deletions(-) create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountCredentials.swift rename Multiplatform/macOS/Preferences/Preference Panes/Accounts/{ => Edit Account}/EditAccountView.swift (96%) diff --git a/Multiplatform/Shared/AppDefaults.swift b/Multiplatform/Shared/AppDefaults.swift index 52452c851..b5f998d46 100644 --- a/Multiplatform/Shared/AppDefaults.swift +++ b/Multiplatform/Shared/AppDefaults.swift @@ -138,6 +138,17 @@ final class AppDefaults: ObservableObject { } } + static var userInterfaceColorScheme: ColorScheme? { + switch AppDefaults.shared.userInterfaceColorPalette { + case .light: + return ColorScheme.light + case .dark: + return ColorScheme.dark + default: + return nil + } + } + // MARK: Feeds & Folders @AppStorage(Key.addWebFeedAccountID, store: store) var addWebFeedAccountID: String? diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index 9f60267f4..0b5a0f893 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -20,12 +20,14 @@ struct MainApp: App { @StateObject private var defaults = AppDefaults.shared + @SceneBuilder var body: some Scene { #if os(macOS) WindowGroup { SceneNavigationView() .frame(minWidth: 600, idealWidth: 1000, maxWidth: .infinity, minHeight: 600, idealHeight: 700, maxHeight: .infinity) .environmentObject(defaults) + .preferredColorScheme(AppDefaults.userInterfaceColorScheme) } .windowToolbarStyle(UnifiedWindowToolbarStyle()) .commands { @@ -69,6 +71,7 @@ struct MainApp: App { }) } + // Mac Preferences Settings { MacPreferencesView() diff --git a/Multiplatform/macOS/Preferences/MacPreferencesView.swift b/Multiplatform/macOS/Preferences/MacPreferencesView.swift index e663647ad..e24f8d6e9 100644 --- a/Multiplatform/macOS/Preferences/MacPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesView.swift @@ -66,6 +66,8 @@ struct MacPreferencesView: View { } } } + .preferredColorScheme(AppDefaults.userInterfaceColorScheme) + } } diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift index 8fdf7ecbb..74bb43a8b 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift @@ -12,6 +12,12 @@ import Combine class AccountsPreferencesModel: ObservableObject { + enum AccountConfigurationSheets { + case add, credentials, none + } + + public private(set) var account: Account? + // Configured Accounts @Published var sortedAccounts: [Account] = [] @Published var selectedConfiguredAccountID: String? = AccountManager.shared.defaultAccount.accountID { @@ -24,9 +30,17 @@ class AccountsPreferencesModel: ObservableObject { } } @Published var showAddAccountView: Bool = false + var selectedAccountIsDefault: Bool { + guard let selected = selectedConfiguredAccountID else { + return true + } + if selected == AccountManager.shared.defaultAccount.accountID { + return true + } + return false + } // Edit Account - public private(set) var account: Account? @Published var accountIsActive: Bool = false { didSet { account?.isActive = accountIsActive @@ -37,16 +51,18 @@ class AccountsPreferencesModel: ObservableObject { account?.name = accountName } } + @Published var showAddCredentialsView: Bool = false - var selectedAccountIsDefault: Bool { - guard let selected = selectedConfiguredAccountID else { - return true + // Sheets + @Published var showSheet: Bool = false + @Published var sheetToShow: AccountConfigurationSheets = .none { + didSet { + showSheet = sheetToShow != .none } - if selected == AccountManager.shared.defaultAccount.accountID { - return true - } - return false } + @Published var showDeleteConfirmation: Bool = false + + // Subscriptions var notificationSubscriptions = Set() diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift index 858efc679..52fca6143 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift @@ -18,36 +18,59 @@ struct AccountsPreferencesView: View { var body: some View { VStack { HStack(alignment: .top, spacing: 10) { - VStack(alignment: .leading) { - List(viewModel.sortedAccounts, id: \.accountID, selection: $viewModel.selectedConfiguredAccountID) { - ConfiguredAccountRow(account: $0) - .id($0.accountID) - }.overlay( - Group { - bottomButtonStack - }, alignment: .bottom) - } - .frame(width: 160, height: 300, alignment: .leading) - .border(Color.gray, width: 1) + listOfAccounts EditAccountView(viewModel: viewModel) .frame(height: 300, alignment: .leading) } Spacer() - }.sheet(isPresented: $viewModel.showAddAccountView, - onDismiss: { viewModel.showAddAccountView.toggle() }, + } + .sheet(isPresented: $viewModel.showSheet, + onDismiss: { viewModel.sheetToShow = .none }, content: { - AddAccountView(preferencesModel: viewModel) + switch viewModel.sheetToShow { + case .add: + AddAccountView(preferencesModel: viewModel) + case .credentials: + EditAccountCredentials(viewModel: viewModel) + case .none: + EmptyView() + } }) + .alert(isPresented: $viewModel.showDeleteConfirmation, content: { + Alert(title: Text("Delete \(viewModel.account!.nameForDisplay)?"), + message: Text("Are you sure you want to delete the account \"\(viewModel.account!.nameForDisplay)\"? This can not be undone."), + primaryButton: .destructive(Text("Delete"), action: { + AccountManager.shared.deleteAccount(viewModel.account!) + viewModel.showDeleteConfirmation = false + }), + secondaryButton: .cancel({ + viewModel.showDeleteConfirmation = false + })) + }) } + var listOfAccounts: some View { + VStack(alignment: .leading) { + List(viewModel.sortedAccounts, id: \.accountID, selection: $viewModel.selectedConfiguredAccountID) { + ConfiguredAccountRow(account: $0) + .id($0.accountID) + }.overlay( + Group { + bottomButtonStack + }, alignment: .bottom) + } + .frame(width: 160, height: 300, alignment: .leading) + .border(Color.gray, width: 1) + } + var bottomButtonStack: some View { VStack(alignment: .leading, spacing: 0) { Divider() HStack(alignment: .center, spacing: 4) { Button(action: { - viewModel.showAddAccountView.toggle() + viewModel.sheetToShow = .add }, label: { Image(systemName: "plus") .font(.title) @@ -63,10 +86,7 @@ struct AccountsPreferencesView: View { .help("Add Account") Button(action: { - if let account = viewModel.sortedAccounts.first(where: { $0.accountID == viewModel.selectedConfiguredAccountID }) { - AccountManager.shared.deleteAccount(account) - } - + viewModel.showDeleteConfirmation = true }, label: { Image(systemName: "minus") .font(.title) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountCredentials.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountCredentials.swift new file mode 100644 index 000000000..fe868c12a --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountCredentials.swift @@ -0,0 +1,69 @@ +// +// EditAccountCredentials.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 14/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Secrets + +struct EditAccountCredentials: View { + + @ObservedObject var viewModel: AccountsPreferencesModel + @Environment(\.presentationMode) var presentationMode + + @State private var userName: String = "" + @State private var password: String = "" + @State private var apiUrl: String? + + var body: some View { + Form { + HStack { + Spacer() + Image(rsImage: viewModel.account!.smallIcon!.image) + .resizable() + .frame(width: 30, height: 30) + Text(viewModel.account?.nameForDisplay ?? "") + Spacer() + }.padding() + + HStack(alignment: .center) { + VStack(alignment: .trailing, spacing: 12) { + Text("Username: ") + Text("Password: ") + }.frame(width: 75) + + VStack(alignment: .leading, spacing: 12) { + TextField("Username", text: $userName) + SecureField("Password", text: $password) + } + }.textFieldStyle(RoundedBorderTextFieldStyle()) + + Spacer() + HStack{ + Spacer() + Button("Dismiss", action: { + presentationMode.wrappedValue.dismiss() + }) + Button("Update", action: { + presentationMode.wrappedValue.dismiss() + }) + } + }.onAppear { + let credentials = try? viewModel.account?.retrieveCredentials(type: .basic) + userName = credentials?.username ?? "" + password = credentials?.secret ?? "" + } + .frame(idealWidth: 300, idealHeight: 200, alignment: .top) + .padding() + } +} + +struct EditAccountCredentials_Previews: PreviewProvider { + static var previews: some View { + EditAccountCredentials(viewModel: AccountsPreferencesModel()) + } +} + diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountView.swift similarity index 96% rename from Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift rename to Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountView.swift index 6999f22b0..87d4761b6 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/EditAccountView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountView.swift @@ -15,7 +15,6 @@ struct EditAccountView: View { @ObservedObject var viewModel: AccountsPreferencesModel var body: some View { - ZStack { RoundedRectangle(cornerRadius: 8, style: .circular) .foregroundColor(Color.secondary.opacity(0.1)) @@ -23,11 +22,9 @@ struct EditAccountView: View { VStack { editAccountHeader - if viewModel.account != nil { editAccountForm } - Spacer() } } @@ -67,12 +64,14 @@ struct EditAccountView: View { HStack { Spacer() Button("Credentials", action: { - + viewModel.sheetToShow = .credentials }) Spacer() } } - }).padding() + }) + .padding() + } } diff --git a/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift index 8d0056150..f0bb30ded 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift @@ -10,7 +10,9 @@ import SwiftUI struct GeneralPreferencesView: View { @EnvironmentObject private var defaults: AppDefaults + @Environment(\.colorScheme) private var colorScheme @ObservedObject var preferences: MacPreferencesModel + private let colorPalettes = UserInterfaceColorPalette.allCases var body: some View { Form { @@ -32,8 +34,6 @@ struct GeneralPreferencesView: View { Text(preferences.rssReaders[index].nameMinusAppSuffix) .tag(index) } - - }) }) @@ -41,6 +41,16 @@ struct GeneralPreferencesView: View { Toggle("Open webpages in background in browser", isOn: $defaults.openInBrowserInBackground) Toggle("Hide Unread Count in Dock", isOn: $defaults.hideDockUnreadCount) + + Divider() + + Picker("Appearance", selection: $defaults.userInterfaceColorPalette, content: { + ForEach(colorPalettes, id: \.self, content: { + Text($0.description) + }) + }).pickerStyle(RadioGroupPickerStyle()) + + } .frame(width: 400, alignment: .center) .lineLimit(2) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 86e7de53d..6cd60e8e0 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 1769E32924BCAFC7000E1E8E /* AddAccountPickerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */; }; 1769E32B24BCB030000E1E8E /* ConfiguredAccountRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */; }; 1769E32D24BD20A0000E1E8E /* EditAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32C24BD20A0000E1E8E /* EditAccountView.swift */; }; + 1769E33024BD6271000E1E8E /* EditAccountCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32F24BD6271000E1E8E /* EditAccountCredentials.swift */; }; 1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; }; @@ -1800,6 +1801,7 @@ 1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountPickerRow.swift; sourceTree = ""; }; 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfiguredAccountRow.swift; sourceTree = ""; }; 1769E32C24BD20A0000E1E8E /* EditAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountView.swift; sourceTree = ""; }; + 1769E32F24BD6271000E1E8E /* EditAccountCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountCredentials.swift; sourceTree = ""; }; 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.swift; sourceTree = ""; }; 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = ""; }; @@ -2548,7 +2550,7 @@ 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */, 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */, 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */, - 1769E32C24BD20A0000E1E8E /* EditAccountView.swift */, + 1769E32E24BD5F22000E1E8E /* Edit Account */, 1769E32324BC5A50000E1E8E /* Add Account */, ); path = Accounts; @@ -2572,6 +2574,15 @@ path = "Add Account"; sourceTree = ""; }; + 1769E32E24BD5F22000E1E8E /* Edit Account */ = { + isa = PBXGroup; + children = ( + 1769E32C24BD20A0000E1E8E /* EditAccountView.swift */, + 1769E32F24BD6271000E1E8E /* EditAccountCredentials.swift */, + ); + path = "Edit Account"; + sourceTree = ""; + }; 17930ED224AF10CD00A9BA52 /* Add */ = { isa = PBXGroup; children = ( @@ -5252,6 +5263,7 @@ 51408B7F24A9EC6F0073CF4E /* SidebarItem.swift in Sources */, 514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */, 51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */, + 1769E33024BD6271000E1E8E /* EditAccountCredentials.swift in Sources */, 51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */, 51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */, 51A5769724AE617200078888 /* ArticleContainerView.swift in Sources */, From 06af59fb2bd6cd3bf29db5d741df8588a935f3f0 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 14 Jul 2020 15:05:11 +0800 Subject: [PATCH 17/28] fixes dark mode on accounts overlay --- .../Preference Panes/Accounts/AccountsPreferencesView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift index 52fca6143..a3b025039 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift @@ -104,7 +104,7 @@ struct AccountsPreferencesView: View { Spacer() } - .background(Color.white) + .background(Color.init(.windowBackgroundColor)) } } From aca43090f866efcceefe8a84c29874a8e321cd0e Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 14 Jul 2020 16:25:37 +0800 Subject: [PATCH 18/28] Edit account Edit account now has its own model Refactored account creation and updated errors into separate enum Renamed some structs --- .../AccountsPreferencesModel.swift | 5 +- .../AccountsPreferencesView.swift | 5 +- .../Add Account/AddAccountModel.swift | 34 +-- .../Add Account/AddAccountPickerRow.swift | 0 .../Add Account/AddAccountView.swift | 7 +- .../ConfiguredAccountRow.swift | 0 .../Edit Account/AccountDetailView.swift} | 8 +- .../EditAccountCredentialsModel.swift | 262 ++++++++++++++++++ .../EditAccountCredentialsView.swift | 94 +++++++ .../Accounts/AccountManagement.swift | 18 ++ .../Accounts/AccountUpdateErrors.swift | 39 +++ .../Edit Account/EditAccountCredentials.swift | 69 ----- NetNewsWire.xcodeproj/project.pbxproj | 42 ++- 13 files changed, 455 insertions(+), 128 deletions(-) rename Multiplatform/macOS/Preferences/Preference Panes/Accounts/{ => Account Preferences}/AccountsPreferencesModel.swift (98%) rename Multiplatform/macOS/Preferences/Preference Panes/Accounts/{ => Account Preferences}/AccountsPreferencesView.swift (96%) rename Multiplatform/macOS/Preferences/Preference Panes/Accounts/{ => Account Preferences}/Add Account/AddAccountModel.swift (82%) rename Multiplatform/macOS/Preferences/Preference Panes/Accounts/{ => Account Preferences}/Add Account/AddAccountPickerRow.swift (100%) rename Multiplatform/macOS/Preferences/Preference Panes/Accounts/{ => Account Preferences}/Add Account/AddAccountView.swift (95%) rename Multiplatform/macOS/Preferences/Preference Panes/Accounts/{ => Account Preferences}/ConfiguredAccountRow.swift (100%) rename Multiplatform/macOS/Preferences/Preference Panes/Accounts/{Edit Account/EditAccountView.swift => Account Preferences/Edit Account/AccountDetailView.swift} (90%) create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountManagement.swift create mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountUpdateErrors.swift delete mode 100644 Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountCredentials.swift diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift similarity index 98% rename from Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift rename to Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift index 74bb43a8b..c7683af0e 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift @@ -16,9 +16,10 @@ class AccountsPreferencesModel: ObservableObject { case add, credentials, none } + // Selected Account public private(set) var account: Account? - // Configured Accounts + // All Accounts @Published var sortedAccounts: [Account] = [] @Published var selectedConfiguredAccountID: String? = AccountManager.shared.defaultAccount.accountID { didSet { @@ -62,8 +63,6 @@ class AccountsPreferencesModel: ObservableObject { } @Published var showDeleteConfirmation: Bool = false - - // Subscriptions var notificationSubscriptions = Set() diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesView.swift similarity index 96% rename from Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift rename to Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesView.swift index a3b025039..20261fb98 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountsPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesView.swift @@ -20,7 +20,7 @@ struct AccountsPreferencesView: View { HStack(alignment: .top, spacing: 10) { listOfAccounts - EditAccountView(viewModel: viewModel) + AccountDetailView(viewModel: viewModel) .frame(height: 300, alignment: .leading) } Spacer() @@ -32,7 +32,7 @@ struct AccountsPreferencesView: View { case .add: AddAccountView(preferencesModel: viewModel) case .credentials: - EditAccountCredentials(viewModel: viewModel) + EditAccountCredentialsView(viewModel: viewModel) case .none: EmptyView() } @@ -48,7 +48,6 @@ struct AccountsPreferencesView: View { viewModel.showDeleteConfirmation = false })) }) - } var listOfAccounts: some View { diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift similarity index 82% rename from Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift rename to Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift index b2a5a7a10..31f2976ba 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift @@ -13,35 +13,7 @@ import Secrets class AddAccountModel: ObservableObject { - enum AddAccountErrors: CustomStringConvertible { - case invalidUsernamePassword, invalidUsernamePasswordAPI, networkError, keyChainError, other(error: Error) , none - - var description: String { - switch self { - case .invalidUsernamePassword: - return NSLocalizedString("Invalid email or password combination.", comment: "Invalid email/password combination.") - case .invalidUsernamePasswordAPI: - return NSLocalizedString("Invalid email, password, or API URL combination.", comment: "Invalid email/password/API combination.") - case .networkError: - return NSLocalizedString("Network Error. Please try later.", comment: "Network Error. Please try later.") - case .keyChainError: - return NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") - case .other(let error): - return NSLocalizedString(error.localizedDescription, comment: "Other add account error") - default: - return NSLocalizedString("N/A", comment: "N/A") - } - } - - static func ==(lhs: AddAccountErrors, rhs: AddAccountErrors) -> Bool { - switch (lhs, rhs) { - case (.other(let lhsError), .other(let rhsError)): - return lhsError.localizedDescription == rhsError.localizedDescription - default: - return false - } - } - } + #if DEBUG let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly, .feedWrangler, .freshRSS, .cloudKit, .newsBlur] @@ -56,7 +28,7 @@ class AddAccountModel: ObservableObject { @Published var apiUrl: String = "" @Published var newLocalAccountName: String = "" @Published var accountIsAuthenticating: Bool = false - @Published var addAccountError: AddAccountErrors = .none { + @Published var addAccountError: AccountUpdateErrors = .none { didSet { if addAccountError == .none { showError = false @@ -261,7 +233,7 @@ extension AddAccountModel { return } - let account = AccountManager.shared.createAccount(type: .newsBlur) + let account = AccountManager.shared.createAccount(type: .freshRSS) do { try account.removeCredentials(type: .readerBasic) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountPickerRow.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountPickerRow.swift similarity index 100% rename from Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountPickerRow.swift rename to Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountPickerRow.swift diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift similarity index 95% rename from Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift rename to Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift index 8be44fee2..48e0dcd6d 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Add Account/AddAccountView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift @@ -50,7 +50,7 @@ struct AddAccountView: View { Spacer() HStack { if viewModel.accountIsAuthenticating { - ProgressView() + ProgressView("Adding Account") } Spacer() Button(action: { presentationMode.wrappedValue.dismiss() }, label: { @@ -121,12 +121,9 @@ struct AddAccountView: View { var userNamePasswordAndAPIUrlView: some View { Group { TextField("Email", text: $viewModel.userName) - .textFieldStyle(RoundedBorderTextFieldStyle()) SecureField("Password", text: $viewModel.password) - .textFieldStyle(RoundedBorderTextFieldStyle()) TextField("API URL", text: $viewModel.apiUrl) - .textFieldStyle(RoundedBorderTextFieldStyle()) - } + }.textFieldStyle(RoundedBorderTextFieldStyle()) } diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/ConfiguredAccountRow.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/ConfiguredAccountRow.swift similarity index 100% rename from Multiplatform/macOS/Preferences/Preference Panes/Accounts/ConfiguredAccountRow.swift rename to Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/ConfiguredAccountRow.swift diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/AccountDetailView.swift similarity index 90% rename from Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountView.swift rename to Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/AccountDetailView.swift index 87d4761b6..f979159ec 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/AccountDetailView.swift @@ -1,5 +1,5 @@ // -// EditAccountView.swift +// AccountDetailView.swift // Multiplatform macOS // // Created by Stuart Breckenridge on 14/7/20. @@ -10,7 +10,7 @@ import SwiftUI import Account import Combine -struct EditAccountView: View { +struct AccountDetailView: View { @ObservedObject var viewModel: AccountsPreferencesModel @@ -76,8 +76,8 @@ struct EditAccountView: View { } -struct EditAccountView_Previews: PreviewProvider { +struct AccountDetailView_Previews: PreviewProvider { static var previews: some View { - EditAccountView(viewModel: AccountsPreferencesModel()) + AccountDetailView(viewModel: AccountsPreferencesModel()) } } diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift new file mode 100644 index 000000000..55dd2e323 --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift @@ -0,0 +1,262 @@ +// +// EditAccountCredentialsModel.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 14/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import Account +import Secrets + +class EditAccountCredentialsModel: ObservableObject { + + @Published var userName: String = "" + @Published var password: String = "" + @Published var apiUrl: String = "" + @Published var accountIsUpdatingCredentials: Bool = false + @Published var accountCredentialsWereUpdated: Bool = false + @Published var error: AccountUpdateErrors = .none { + didSet { + if error == .none { + showError = false + } else { + showError = true + } + } + } + @Published var showError: Bool = false + + func updateAccountCredentials(_ account: Account) { + switch account.type { + case .onMyMac: + return + case .feedbin: + updateFeedbin(account) + case .cloudKit: + return + case .feedWrangler: + updateFeedWrangler(account) + case .feedly: + updateFeedly(account) + case .freshRSS: + updateFreshRSS(account) + case .newsBlur: + updateNewsblur(account) + } + } + + func retrieveCredentials(_ account: Account) { + switch account.type { + case .feedbin: + let credentials = try? account.retrieveCredentials(type: .basic) + userName = credentials?.username ?? "" + password = credentials?.secret ?? "" + case .feedWrangler: + let credentials = try? account.retrieveCredentials(type: .feedWranglerBasic) + userName = credentials?.username ?? "" + password = credentials?.secret ?? "" + case .freshRSS: + let credentials = try? account.retrieveCredentials(type: .readerBasic) + userName = credentials?.username ?? "" + password = credentials?.secret ?? "" + case .newsBlur: + let credentials = try? account.retrieveCredentials(type: .newsBlurBasic) + userName = credentials?.username ?? "" + password = credentials?.secret ?? "" + default: + return + } + } + +} + +// MARK:- Update API +extension EditAccountCredentialsModel { + + func updateFeedbin(_ account: Account) { + accountIsUpdatingCredentials = true + let credentials = Credentials(type: .basic, username: userName, secret: password) + + Account.validateCredentials(type: .feedbin, credentials: credentials) { [weak self] result in + + guard let self = self else { return } + + self.accountIsUpdatingCredentials = false + + switch result { + case .success(let validatedCredentials): + + guard let validatedCredentials = validatedCredentials else { + self.error = .invalidUsernamePassword + return + } + + do { + try account.removeCredentials(type: .basic) + try account.storeCredentials(validatedCredentials) + self.accountCredentialsWereUpdated = true + account.refreshAll(completion: { result in + switch result { + case .success: + break + case .failure(let error): + self.error = .other(error: error) + } + }) + + } catch { + self.error = .keyChainError + } + + case .failure: + self.error = .networkError + } + } + } + + func updateFeedWrangler(_ account: Account) { + accountIsUpdatingCredentials = true + let credentials = Credentials(type: .feedWranglerBasic, username: userName, secret: password) + + Account.validateCredentials(type: .feedWrangler, credentials: credentials) { [weak self] result in + + guard let self = self else { return } + + self.accountIsUpdatingCredentials = false + + switch result { + case .success(let validatedCredentials): + + guard let validatedCredentials = validatedCredentials else { + self.error = .invalidUsernamePassword + return + } + + do { + try account.removeCredentials(type: .feedWranglerBasic) + try account.removeCredentials(type: .feedWranglerToken) + try account.storeCredentials(credentials) + try account.storeCredentials(validatedCredentials) + self.accountCredentialsWereUpdated = true + account.refreshAll(completion: { result in + switch result { + case .success: + break + case .failure(let error): + self.error = .other(error: error) + } + }) + + } catch { + self.error = .keyChainError + } + + case .failure: + self.error = .networkError + } + } + } + + func updateFeedly(_ account: Account) { + + } + + func updateFreshRSS(_ account: Account) { + accountIsUpdatingCredentials = true + let credentials = Credentials(type: .readerBasic, username: userName, secret: password) + + Account.validateCredentials(type: .freshRSS, credentials: credentials) { [weak self] result in + + guard let self = self else { return } + + self.accountIsUpdatingCredentials = false + + switch result { + case .success(let validatedCredentials): + + guard let validatedCredentials = validatedCredentials else { + self.error = .invalidUsernamePassword + return + } + + do { + try account.removeCredentials(type: .readerBasic) + try account.removeCredentials(type: .readerAPIKey) + try account.storeCredentials(credentials) + try account.storeCredentials(validatedCredentials) + self.accountCredentialsWereUpdated = true + account.refreshAll(completion: { result in + switch result { + case .success: + break + case .failure(let error): + self.error = .other(error: error) + } + }) + + } catch { + self.error = .keyChainError + } + + case .failure: + self.error = .networkError + } + } + } + + func updateNewsblur(_ account: Account) { + accountIsUpdatingCredentials = true + let credentials = Credentials(type: .newsBlurBasic, username: userName, secret: password) + + Account.validateCredentials(type: .newsBlur, credentials: credentials) { [weak self] result in + + guard let self = self else { return } + + self.accountIsUpdatingCredentials = false + + switch result { + case .success(let validatedCredentials): + + guard let validatedCredentials = validatedCredentials else { + self.error = .invalidUsernamePassword + return + } + + do { + try account.removeCredentials(type: .newsBlurBasic) + try account.removeCredentials(type: .newsBlurSessionId) + try account.storeCredentials(credentials) + try account.storeCredentials(validatedCredentials) + self.accountCredentialsWereUpdated = true + account.refreshAll(completion: { result in + switch result { + case .success: + break + case .failure(let error): + self.error = .other(error: error) + } + }) + + } catch { + self.error = .keyChainError + } + + case .failure: + self.error = .networkError + } + } + } + +} + +// MARK:- Retrieve Credentials +extension EditAccountCredentialsModel { + + + + + + +} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift new file mode 100644 index 000000000..8d2054960 --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift @@ -0,0 +1,94 @@ +// +// EditAccountCredentialsView.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 14/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Secrets + +struct EditAccountCredentialsView: View { + + @Environment(\.presentationMode) var presentationMode + @StateObject private var editModel = EditAccountCredentialsModel() + @ObservedObject var viewModel: AccountsPreferencesModel + + var body: some View { + Form { + HStack { + Spacer() + Image(rsImage: viewModel.account!.smallIcon!.image) + .resizable() + .frame(width: 30, height: 30) + Text(viewModel.account?.nameForDisplay ?? "") + Spacer() + }.padding() + + HStack(alignment: .center) { + VStack(alignment: .trailing, spacing: 12) { + Text("Username: ") + Text("Password: ") + if viewModel.account?.type == .freshRSS { + Text("API URL: ") + } + }.frame(width: 75) + + VStack(alignment: .leading, spacing: 12) { + TextField("Username", text: $editModel.userName) + SecureField("Password", text: $editModel.password) + if viewModel.account?.type == .freshRSS { + TextField("API URL", text: $editModel.apiUrl) + } + } + }.textFieldStyle(RoundedBorderTextFieldStyle()) + + Spacer() + HStack{ + if editModel.accountIsUpdatingCredentials { + ProgressView("Updating") + } + Spacer() + Button("Cancel", action: { + presentationMode.wrappedValue.dismiss() + }) + if viewModel.account?.type != .freshRSS { + Button("Update", action: { + editModel.updateAccountCredentials(viewModel.account!) + }).disabled(editModel.userName.count == 0 || editModel.password.count == 0) + } else { + Button("Update", action: { + editModel.updateAccountCredentials(viewModel.account!) + }).disabled(editModel.userName.count == 0 || editModel.password.count == 0 || editModel.apiUrl.count == 0) + } + + } + }.onAppear { + editModel.retrieveCredentials(viewModel.account!) + } + .onChange(of: editModel.accountCredentialsWereUpdated) { value in + if value == true { + viewModel.sheetToShow = .none + } + } + .alert(isPresented: $editModel.showError) { + Alert(title: Text("Error Adding Account"), + message: Text(editModel.error.description), + dismissButton: .default(Text("Dismiss"), + action: { + editModel.error = .none + })) + } + .frame(idealWidth: 300, idealHeight: 200, alignment: .top) + .padding() + + } +} + +struct EditAccountCredentials_Previews: PreviewProvider { + static var previews: some View { + EditAccountCredentialsView(viewModel: AccountsPreferencesModel()) + } +} + diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountManagement.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountManagement.swift new file mode 100644 index 000000000..d734c676c --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountManagement.swift @@ -0,0 +1,18 @@ +// +// AccountManagement.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 14/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation + +protocol AccountManagement { + + + + + + +} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountUpdateErrors.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountUpdateErrors.swift new file mode 100644 index 000000000..0ecec3500 --- /dev/null +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountUpdateErrors.swift @@ -0,0 +1,39 @@ +// +// AccountUpdateErrors.swift +// Multiplatform macOS +// +// Created by Stuart Breckenridge on 14/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation + +enum AccountUpdateErrors: CustomStringConvertible { + case invalidUsernamePassword, invalidUsernamePasswordAPI, networkError, keyChainError, other(error: Error) , none + + var description: String { + switch self { + case .invalidUsernamePassword: + return NSLocalizedString("Invalid email or password combination.", comment: "Invalid email/password combination.") + case .invalidUsernamePasswordAPI: + return NSLocalizedString("Invalid email, password, or API URL combination.", comment: "Invalid email/password/API combination.") + case .networkError: + return NSLocalizedString("Network Error. Please try later.", comment: "Network Error. Please try later.") + case .keyChainError: + return NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") + case .other(let error): + return NSLocalizedString(error.localizedDescription, comment: "Other add account error") + default: + return NSLocalizedString("N/A", comment: "N/A") + } + } + + static func ==(lhs: AccountUpdateErrors, rhs: AccountUpdateErrors) -> Bool { + switch (lhs, rhs) { + case (.other(let lhsError), .other(let rhsError)): + return lhsError.localizedDescription == rhsError.localizedDescription + default: + return false + } + } +} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountCredentials.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountCredentials.swift deleted file mode 100644 index fe868c12a..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Edit Account/EditAccountCredentials.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// EditAccountCredentials.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 14/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Secrets - -struct EditAccountCredentials: View { - - @ObservedObject var viewModel: AccountsPreferencesModel - @Environment(\.presentationMode) var presentationMode - - @State private var userName: String = "" - @State private var password: String = "" - @State private var apiUrl: String? - - var body: some View { - Form { - HStack { - Spacer() - Image(rsImage: viewModel.account!.smallIcon!.image) - .resizable() - .frame(width: 30, height: 30) - Text(viewModel.account?.nameForDisplay ?? "") - Spacer() - }.padding() - - HStack(alignment: .center) { - VStack(alignment: .trailing, spacing: 12) { - Text("Username: ") - Text("Password: ") - }.frame(width: 75) - - VStack(alignment: .leading, spacing: 12) { - TextField("Username", text: $userName) - SecureField("Password", text: $password) - } - }.textFieldStyle(RoundedBorderTextFieldStyle()) - - Spacer() - HStack{ - Spacer() - Button("Dismiss", action: { - presentationMode.wrappedValue.dismiss() - }) - Button("Update", action: { - presentationMode.wrappedValue.dismiss() - }) - } - }.onAppear { - let credentials = try? viewModel.account?.retrieveCredentials(type: .basic) - userName = credentials?.username ?? "" - password = credentials?.secret ?? "" - } - .frame(idealWidth: 300, idealHeight: 200, alignment: .top) - .padding() - } -} - -struct EditAccountCredentials_Previews: PreviewProvider { - static var previews: some View { - EditAccountCredentials(viewModel: AccountsPreferencesModel()) - } -} - diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 6cd60e8e0..923e1ee06 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -23,8 +23,10 @@ 1769E32724BC5B6C000E1E8E /* AddAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */; }; 1769E32924BCAFC7000E1E8E /* AddAccountPickerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */; }; 1769E32B24BCB030000E1E8E /* ConfiguredAccountRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */; }; - 1769E32D24BD20A0000E1E8E /* EditAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32C24BD20A0000E1E8E /* EditAccountView.swift */; }; - 1769E33024BD6271000E1E8E /* EditAccountCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32F24BD6271000E1E8E /* EditAccountCredentials.swift */; }; + 1769E32D24BD20A0000E1E8E /* AccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32C24BD20A0000E1E8E /* AccountDetailView.swift */; }; + 1769E33024BD6271000E1E8E /* EditAccountCredentialsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32F24BD6271000E1E8E /* EditAccountCredentialsView.swift */; }; + 1769E33624BD9621000E1E8E /* EditAccountCredentialsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */; }; + 1769E33824BD97CB000E1E8E /* AccountUpdateErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */; }; 1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; }; @@ -1800,8 +1802,10 @@ 1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountModel.swift; sourceTree = ""; }; 1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountPickerRow.swift; sourceTree = ""; }; 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfiguredAccountRow.swift; sourceTree = ""; }; - 1769E32C24BD20A0000E1E8E /* EditAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountView.swift; sourceTree = ""; }; - 1769E32F24BD6271000E1E8E /* EditAccountCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountCredentials.swift; sourceTree = ""; }; + 1769E32C24BD20A0000E1E8E /* AccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDetailView.swift; sourceTree = ""; }; + 1769E32F24BD6271000E1E8E /* EditAccountCredentialsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountCredentialsView.swift; sourceTree = ""; }; + 1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountCredentialsModel.swift; sourceTree = ""; }; + 1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountUpdateErrors.swift; sourceTree = ""; }; 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.swift; sourceTree = ""; }; 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = ""; }; @@ -2547,11 +2551,8 @@ 1769E31F24BC58A4000E1E8E /* Accounts */ = { isa = PBXGroup; children = ( - 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */, - 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */, - 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */, - 1769E32E24BD5F22000E1E8E /* Edit Account */, - 1769E32324BC5A50000E1E8E /* Add Account */, + 1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */, + 1769E33924BD97E5000E1E8E /* Account Preferences */, ); path = Accounts; sourceTree = ""; @@ -2577,12 +2578,25 @@ 1769E32E24BD5F22000E1E8E /* Edit Account */ = { isa = PBXGroup; children = ( - 1769E32C24BD20A0000E1E8E /* EditAccountView.swift */, - 1769E32F24BD6271000E1E8E /* EditAccountCredentials.swift */, + 1769E32C24BD20A0000E1E8E /* AccountDetailView.swift */, + 1769E32F24BD6271000E1E8E /* EditAccountCredentialsView.swift */, + 1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */, ); path = "Edit Account"; sourceTree = ""; }; + 1769E33924BD97E5000E1E8E /* Account Preferences */ = { + isa = PBXGroup; + children = ( + 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */, + 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */, + 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */, + 1769E32324BC5A50000E1E8E /* Add Account */, + 1769E32E24BD5F22000E1E8E /* Edit Account */, + ); + path = "Account Preferences"; + sourceTree = ""; + }; 17930ED224AF10CD00A9BA52 /* Add */ = { isa = PBXGroup; children = ( @@ -5193,6 +5207,7 @@ 51E4996F24A8764C00B667CB /* ActivityType.swift in Sources */, 51E4994E24A8734C00B667CB /* SendToMarsEditCommand.swift in Sources */, 51919FB024AA8EFA00541E64 /* SidebarItemView.swift in Sources */, + 1769E33624BD9621000E1E8E /* EditAccountCredentialsModel.swift in Sources */, 51919FEF24AB85E400541E64 /* TimelineContainerView.swift in Sources */, 51E4996624A8760B00B667CB /* ArticleStyle.swift in Sources */, 5171B4D424B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */, @@ -5217,6 +5232,7 @@ 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, 51E498CA24A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, 5177470A24B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */, + 1769E33824BD97CB000E1E8E /* AccountUpdateErrors.swift in Sources */, 51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */, 5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */, 1769E32924BCAFC7000E1E8E /* AddAccountPickerRow.swift in Sources */, @@ -5233,7 +5249,7 @@ 1729529724AA1CD000D65E66 /* MacPreferencesView.swift in Sources */, 51E4994C24A8734C00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, 1729529324AA1CAA00D65E66 /* AccountsPreferencesView.swift in Sources */, - 1769E32D24BD20A0000E1E8E /* EditAccountView.swift in Sources */, + 1769E32D24BD20A0000E1E8E /* AccountDetailView.swift in Sources */, 51919FAD24AA8CCA00541E64 /* UnreadCountView.swift in Sources */, 51E498C924A8085D00B667CB /* PseudoFeed.swift in Sources */, 51E498FC24A808BA00B667CB /* FaviconURLFinder.swift in Sources */, @@ -5263,7 +5279,7 @@ 51408B7F24A9EC6F0073CF4E /* SidebarItem.swift in Sources */, 514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */, 51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */, - 1769E33024BD6271000E1E8E /* EditAccountCredentials.swift in Sources */, + 1769E33024BD6271000E1E8E /* EditAccountCredentialsView.swift in Sources */, 51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */, 51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */, 51A5769724AE617200078888 /* ArticleContainerView.swift in Sources */, From ade441af7436dc0f68e7ec360903757475c1fce0 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 14 Jul 2020 21:14:16 +0800 Subject: [PATCH 19/28] [weak self] --- .../AccountsPreferencesModel.swift | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift index c7683af0e..904f2de29 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift @@ -69,24 +69,24 @@ class AccountsPreferencesModel: ObservableObject { init() { sortedAccounts = AccountManager.shared.sortedAccounts - NotificationCenter.default.publisher(for: .UserDidAddAccount).sink(receiveValue: { _ in - self.sortedAccounts = AccountManager.shared.sortedAccounts + NotificationCenter.default.publisher(for: .UserDidAddAccount).sink(receiveValue: { [weak self] _ in + self?.sortedAccounts = AccountManager.shared.sortedAccounts }).store(in: ¬ificationSubscriptions) - NotificationCenter.default.publisher(for: .UserDidDeleteAccount).sink(receiveValue: { _ in - self.selectedConfiguredAccountID = nil - self.sortedAccounts = AccountManager.shared.sortedAccounts - self.selectedConfiguredAccountID = AccountManager.shared.defaultAccount.accountID + NotificationCenter.default.publisher(for: .UserDidDeleteAccount).sink(receiveValue: { [weak self] _ in + self?.selectedConfiguredAccountID = nil + self?.sortedAccounts = AccountManager.shared.sortedAccounts + self?.selectedConfiguredAccountID = AccountManager.shared.defaultAccount.accountID }).store(in: ¬ificationSubscriptions) - NotificationCenter.default.publisher(for: .AccountStateDidChange).sink(receiveValue: { notification in + NotificationCenter.default.publisher(for: .AccountStateDidChange).sink(receiveValue: { [weak self] notification in guard let account = notification.object as? Account else { return } - if account.accountID == self.account?.accountID { - self.account = account - self.accountIsActive = account.isActive - self.accountName = account.name ?? "" + if account.accountID == self?.account?.accountID { + self?.account = account + self?.accountIsActive = account.isActive + self?.accountName = account.name ?? "" } }).store(in: ¬ificationSubscriptions) } From dfe314f21472804f7b1dda37d0d4c0ab9ff15dd9 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 15 Jul 2020 09:04:33 +0800 Subject: [PATCH 20/28] Adds Feedly and Restricts Accounts --- .../Add Account/AddAccountModel.swift | 57 ++++++++++++-- .../Add Account/AddAccountView.swift | 74 ++++++++++++------- .../EditAccountCredentialsModel.swift | 38 +++++++--- 3 files changed, 125 insertions(+), 44 deletions(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift index 31f2976ba..78a836122 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift @@ -10,15 +10,14 @@ import Foundation import Account import RSWeb import Secrets +import RSCore class AddAccountModel: ObservableObject { - - #if DEBUG - let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly, .feedWrangler, .freshRSS, .cloudKit, .newsBlur] + var addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly, .feedWrangler, .freshRSS, .cloudKit, .newsBlur] #else - let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly] + var addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly] #endif // Add Accounts @@ -40,6 +39,10 @@ class AddAccountModel: ObservableObject { @Published var showError: Bool = false @Published var accountAdded: Bool = false + init() { + restrictAccounts() + } + func resetUserEntries() { userName = "" password = "" @@ -66,6 +69,25 @@ class AddAccountModel: ObservableObject { } } + func restrictAccounts() { + func removeAccountType(_ accountType: AccountType) { + if let index = addableAccountTypes.firstIndex(of: accountType) { + addableAccountTypes.remove(at: index) + } + } + + if AppDefaults.shared.isDeveloperBuild { + removeAccountType(.cloudKit) + removeAccountType(.feedly) + removeAccountType(.feedWrangler) + return + } + + if AccountManager.shared.activeAccounts.firstIndex(where: { $0.type == .cloudKit }) != nil { + removeAccountType(.cloudKit) + } + } + } // MARK:- Authentication APIs @@ -266,7 +288,32 @@ extension AddAccountModel { } private func authenticateFeedly() { - // TBC + accountIsAuthenticating = true + let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly) + addAccount.delegate = self + addAccount.presentationAnchor = NSApplication.shared.windows.last + MainThreadOperationQueue.shared.add(addAccount) } } + +// MARK:- OAuthAccountAuthorizationOperationDelegate +extension AddAccountModel: OAuthAccountAuthorizationOperationDelegate { + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { + accountIsAuthenticating = false + accountAdded = true + account.refreshAll { [weak self] result in + switch result { + case .success: + break + case .failure(let error): + self?.addAccountError = .other(error: error) + } + } + } + + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { + accountIsAuthenticating = false + addAccountError = .other(error: error) + } +} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift index 48e0dcd6d..aa231c3a8 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift @@ -28,12 +28,11 @@ struct AddAccountView: View { }) }) - switch viewModel.selectedAddAccount { case .onMyMac: addLocalAccountView case .cloudKit: - Text("iCloud") + iCloudAccountView case .feedbin: userNameAndPasswordView case .feedWrangler: @@ -41,12 +40,17 @@ struct AddAccountView: View { case .freshRSS: userNamePasswordAndAPIUrlView case .feedly: - Text("Feedly") + oAuthView case .newsBlur: userNameAndPasswordView } + } + + + + Spacer() HStack { if viewModel.accountIsAuthenticating { @@ -57,28 +61,33 @@ struct AddAccountView: View { Text("Cancel") }) - if viewModel.selectedAddAccount == .onMyMac { - Button("Add", action: { - viewModel.authenticateAccount() - }) - } - - if viewModel.selectedAddAccount != .onMyMac && viewModel.selectedAddAccount != .freshRSS { - Button("Add Account", action: { - viewModel.authenticateAccount() - }) - .disabled(viewModel.userName.count == 0 || viewModel.password.count == 0) - } - - if viewModel.selectedAddAccount == .freshRSS { - Button("Add Account", action: { - viewModel.authenticateAccount() - }) - .disabled(viewModel.userName.count == 0 || viewModel.password.count == 0 || viewModel.apiUrl.count == 0) + switch viewModel.selectedAddAccount { + case .onMyMac: + Button("Add Account", action: { + viewModel.authenticateAccount() + }) + case .feedly: + Button("Authenticate", action: { + viewModel.authenticateAccount() + }) + case .cloudKit: + Button("Add Account", action: { + viewModel.authenticateAccount() + }) + case .freshRSS: + Button("Add Account", action: { + viewModel.authenticateAccount() + }) + .disabled(viewModel.userName.count == 0 || viewModel.password.count == 0 || viewModel.apiUrl.count == 0) + default: + Button("Add Account", action: { + viewModel.authenticateAccount() + }) + .disabled(viewModel.userName.count == 0 || viewModel.password.count == 0) } } } - .frame(idealWidth: 300, idealHeight: 200, alignment: .top) + .frame(width: 300, height: 200, alignment: .top) .padding() .onChange(of: viewModel.selectedAddAccount) { _ in viewModel.resetUserEntries() @@ -102,20 +111,25 @@ struct AddAccountView: View { var addLocalAccountView: some View { Group { TextField("Account Name", text: $viewModel.newLocalAccountName) - .textFieldStyle(RoundedBorderTextFieldStyle()) Text("This account stores all of its data on your device. It does not sync.") .foregroundColor(.secondary) .multilineTextAlignment(.leading) - } + }.textFieldStyle(RoundedBorderTextFieldStyle()) + } + + var iCloudAccountView: some View { + Group { + Text("This account syncs across your devices using your iCloud account.") + .foregroundColor(.secondary) + .multilineTextAlignment(.leading) + }.textFieldStyle(RoundedBorderTextFieldStyle()) } var userNameAndPasswordView: some View { Group { TextField("Email", text: $viewModel.userName) - .textFieldStyle(RoundedBorderTextFieldStyle()) SecureField("Password", text: $viewModel.password) - .textFieldStyle(RoundedBorderTextFieldStyle()) - } + }.textFieldStyle(RoundedBorderTextFieldStyle()) } var userNamePasswordAndAPIUrlView: some View { @@ -126,6 +140,12 @@ struct AddAccountView: View { }.textFieldStyle(RoundedBorderTextFieldStyle()) } + var oAuthView: some View { + Group { + Text("Click Authenticate") + }.textFieldStyle(RoundedBorderTextFieldStyle()) + } + } struct AddAccountView_Previews: PreviewProvider { diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift index 55dd2e323..d01103ecb 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift @@ -9,6 +9,7 @@ import Foundation import Account import Secrets +import RSCore class EditAccountCredentialsModel: ObservableObject { @@ -52,19 +53,17 @@ class EditAccountCredentialsModel: ObservableObject { case .feedbin: let credentials = try? account.retrieveCredentials(type: .basic) userName = credentials?.username ?? "" - password = credentials?.secret ?? "" case .feedWrangler: let credentials = try? account.retrieveCredentials(type: .feedWranglerBasic) userName = credentials?.username ?? "" - password = credentials?.secret ?? "" + case .feedly: + return case .freshRSS: let credentials = try? account.retrieveCredentials(type: .readerBasic) userName = credentials?.username ?? "" - password = credentials?.secret ?? "" case .newsBlur: let credentials = try? account.retrieveCredentials(type: .newsBlurBasic) userName = credentials?.username ?? "" - password = credentials?.secret ?? "" default: return } @@ -160,7 +159,11 @@ extension EditAccountCredentialsModel { } func updateFeedly(_ account: Account) { - + accountIsUpdatingCredentials = true + let updateAccount = OAuthAccountAuthorizationOperation(accountType: .feedly) + updateAccount.delegate = self + updateAccount.presentationAnchor = NSApplication.shared.windows.last + MainThreadOperationQueue.shared.add(updateAccount) } func updateFreshRSS(_ account: Account) { @@ -251,12 +254,23 @@ extension EditAccountCredentialsModel { } -// MARK:- Retrieve Credentials -extension EditAccountCredentialsModel { - - - - - +// MARK:- OAuthAccountAuthorizationOperationDelegate +extension EditAccountCredentialsModel: OAuthAccountAuthorizationOperationDelegate { + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { + accountIsUpdatingCredentials = false + accountCredentialsWereUpdated = true + account.refreshAll { [weak self] result in + switch result { + case .success: + break + case .failure(let error): + self?.error = .other(error: error) + } + } + } + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { + accountIsUpdatingCredentials = false + self.error = .other(error: error) + } } From 99b2d3bb0152a058c0dd3e264bf8d83b875edb55 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 15 Jul 2020 09:14:04 +0800 Subject: [PATCH 21/28] removes dead code & correctly dismisses sheets --- .../Accounts/Account Preferences/AccountsPreferencesModel.swift | 1 - .../Account Preferences/Add Account/AddAccountView.swift | 1 + .../Edit Account/EditAccountCredentialsView.swift | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift index 904f2de29..2a9b58240 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift @@ -52,7 +52,6 @@ class AccountsPreferencesModel: ObservableObject { account?.name = accountName } } - @Published var showAddCredentialsView: Bool = false // Sheets @Published var showSheet: Bool = false diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift index aa231c3a8..0d9320e4b 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift @@ -95,6 +95,7 @@ struct AddAccountView: View { .onChange(of: viewModel.accountAdded) { value in if value == true { preferencesModel.showAddAccountView = false + presentationMode.wrappedValue.dismiss() } } .alert(isPresented: $viewModel.showError) { diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift index 8d2054960..4e7ee9710 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift @@ -70,6 +70,7 @@ struct EditAccountCredentialsView: View { .onChange(of: editModel.accountCredentialsWereUpdated) { value in if value == true { viewModel.sheetToShow = .none + presentationMode.wrappedValue.dismiss() } } .alert(isPresented: $editModel.showError) { From c630bf3b9f687d71473ef0305f6150d05a91b4cb Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 15 Jul 2020 10:26:03 +0800 Subject: [PATCH 22/28] Account restrictions removed. --- .../Add Account/AddAccountModel.swift | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift index 78a836122..304fbb5f1 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift @@ -38,11 +38,7 @@ class AddAccountModel: ObservableObject { } @Published var showError: Bool = false @Published var accountAdded: Bool = false - - init() { - restrictAccounts() - } - + func resetUserEntries() { userName = "" password = "" @@ -69,25 +65,6 @@ class AddAccountModel: ObservableObject { } } - func restrictAccounts() { - func removeAccountType(_ accountType: AccountType) { - if let index = addableAccountTypes.firstIndex(of: accountType) { - addableAccountTypes.remove(at: index) - } - } - - if AppDefaults.shared.isDeveloperBuild { - removeAccountType(.cloudKit) - removeAccountType(.feedly) - removeAccountType(.feedWrangler) - return - } - - if AccountManager.shared.activeAccounts.firstIndex(where: { $0.type == .cloudKit }) != nil { - removeAccountType(.cloudKit) - } - } - } // MARK:- Authentication APIs From d8c5d3719c8584b9ff471681c134391dc2b101db Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 15 Jul 2020 10:54:18 +0800 Subject: [PATCH 23/28] Refactors GeneralPrefs Model --- .../Preferences/MacPreferencesView.swift | 34 ++++++++++++++----- .../Add Account/AddAccountModel.swift | 4 +-- .../Add Account/AddAccountView.swift | 7 +--- .../General/GeneralPreferencesModel.swift} | 24 +++---------- .../General/GeneralPreferencesView.swift | 2 +- NetNewsWire.xcodeproj/project.pbxproj | 8 ++--- 6 files changed, 37 insertions(+), 42 deletions(-) rename Multiplatform/macOS/Preferences/{MacPreferencesModel.swift => Preference Panes/General/GeneralPreferencesModel.swift} (86%) diff --git a/Multiplatform/macOS/Preferences/MacPreferencesView.swift b/Multiplatform/macOS/Preferences/MacPreferencesView.swift index e24f8d6e9..1bc3cabcb 100644 --- a/Multiplatform/macOS/Preferences/MacPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesView.swift @@ -7,17 +7,33 @@ import SwiftUI +enum PreferencePane: Int, CaseIterable { + case general = 0 + case accounts = 1 + case advanced = 2 + + var description: String { + switch self { + case .general: + return "General" + case .accounts: + return "Accounts" + case .advanced: + return "Advanced" + } + } +} struct MacPreferencesView: View { @EnvironmentObject var defaults: AppDefaults - @StateObject private var viewModel = MacPreferencesModel() + @State private var preferencePane: PreferencePane = .general var body: some View { VStack { - switch viewModel.currentPreferencePane { + switch preferencePane { case .general: - GeneralPreferencesView(preferences: viewModel) + GeneralPreferencesView() .environmentObject(defaults) case .accounts: AccountsPreferencesView() @@ -31,36 +47,36 @@ struct MacPreferencesView: View { ToolbarItem { HStack { Button(action: { - viewModel.currentPreferencePane = .general + preferencePane = .general }, label: { VStack { Image(systemName: "gearshape") .font(.title2) Text("General") }.foregroundColor( - viewModel.currentPreferencePane == .general ? Color("AccentColor") : Color.gray + preferencePane == .general ? Color("AccentColor") : Color.gray ) }).frame(width: 70) Button(action: { - viewModel.currentPreferencePane = .accounts + preferencePane = .accounts }, label: { VStack { Image(systemName: "at") .font(.title2) Text("Accounts") }.foregroundColor( - viewModel.currentPreferencePane == .accounts ? Color("AccentColor") : Color.gray + preferencePane == .accounts ? Color("AccentColor") : Color.gray ) }).frame(width: 70) Button(action: { - viewModel.currentPreferencePane = .advanced + preferencePane = .advanced }, label: { VStack { Image(systemName: "scale.3d") .font(.title2) Text("Advanced") }.foregroundColor( - viewModel.currentPreferencePane == .advanced ? Color("AccentColor") : Color.gray + preferencePane == .advanced ? Color("AccentColor") : Color.gray ) }).frame(width: 70) } diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift index 304fbb5f1..6354600ba 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountModel.swift @@ -15,9 +15,9 @@ import RSCore class AddAccountModel: ObservableObject { #if DEBUG - var addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly, .feedWrangler, .freshRSS, .cloudKit, .newsBlur] + let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly, .feedWrangler, .freshRSS, .cloudKit, .newsBlur] #else - var addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly] + let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly] #endif // Add Accounts diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift index 0d9320e4b..b9267b6d0 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift @@ -44,13 +44,8 @@ struct AddAccountView: View { case .newsBlur: userNameAndPasswordView } - - } - - - - + Spacer() HStack { if viewModel.accountIsAuthenticating { diff --git a/Multiplatform/macOS/Preferences/MacPreferencesModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesModel.swift similarity index 86% rename from Multiplatform/macOS/Preferences/MacPreferencesModel.swift rename to Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesModel.swift index 765ceffaf..886d58169 100644 --- a/Multiplatform/macOS/Preferences/MacPreferencesModel.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesModel.swift @@ -1,5 +1,5 @@ // -// MacPreferencesModel.swift +// GeneralPreferencesModel.swift // Multiplatform macOS // // Created by Stuart Breckenridge on 12/7/20. @@ -8,26 +8,10 @@ import Foundation -enum PreferencePane: Int, CaseIterable { - case general = 0 - case accounts = 1 - case advanced = 2 - - var description: String { - switch self { - case .general: - return "General" - case .accounts: - return "Accounts" - case .advanced: - return "Advanced" - } - } -} -class MacPreferencesModel: ObservableObject { + +class GeneralPreferencesModel: ObservableObject { - @Published var currentPreferencePane: PreferencePane = PreferencePane.general @Published var rssReaders = [RSSReader]() @Published var readerSelection: Int = 0 { willSet { @@ -47,7 +31,7 @@ class MacPreferencesModel: ObservableObject { // MARK:- RSS Readers -private extension MacPreferencesModel { +private extension GeneralPreferencesModel { func prepareRSSReaders() { diff --git a/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift index f0bb30ded..edd4a3995 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift @@ -11,7 +11,7 @@ struct GeneralPreferencesView: View { @EnvironmentObject private var defaults: AppDefaults @Environment(\.colorScheme) private var colorScheme - @ObservedObject var preferences: MacPreferencesModel + @StateObject private var preferences = GeneralPreferencesModel() private let colorPalettes = UserInterfaceColorPalette.allCases var body: some View { diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 923e1ee06..f67730a6f 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 1717535624BADF33004498C6 /* MacPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1717535524BADF33004498C6 /* MacPreferencesModel.swift */; }; + 1717535624BADF33004498C6 /* GeneralPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */; }; 172199C924AB228900A31D04 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199C824AB228900A31D04 /* SettingsView.swift */; }; 172199ED24AB2E0100A31D04 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199EC24AB2E0100A31D04 /* SafariView.swift */; }; 172199F124AB716900A31D04 /* SidebarToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */; }; @@ -1788,7 +1788,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 1717535524BADF33004498C6 /* MacPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPreferencesModel.swift; sourceTree = ""; }; + 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesModel.swift; sourceTree = ""; }; 172199C824AB228900A31D04 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 172199EC24AB2E0100A31D04 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbarModifier.swift; sourceTree = ""; }; @@ -2523,7 +2523,6 @@ 1729528F24AA1A4F00D65E66 /* Preferences */ = { isa = PBXGroup; children = ( - 1717535524BADF33004498C6 /* MacPreferencesModel.swift */, 1729529624AA1CD000D65E66 /* MacPreferencesView.swift */, 1729529924AA1CE100D65E66 /* Preference Panes */, ); @@ -2543,6 +2542,7 @@ 1769E2FD24BC589E000E1E8E /* General */ = { isa = PBXGroup; children = ( + 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */, 1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */, ); path = General; @@ -5216,7 +5216,7 @@ 51E4996C24A8762D00B667CB /* ExtractedArticle.swift in Sources */, 51E4990824A808C300B667CB /* RSHTMLMetadata+Extension.swift in Sources */, 51919FF824AB8B7700541E64 /* TimelineView.swift in Sources */, - 1717535624BADF33004498C6 /* MacPreferencesModel.swift in Sources */, + 1717535624BADF33004498C6 /* GeneralPreferencesModel.swift in Sources */, 51E4992B24A8676300B667CB /* ArticleArray.swift in Sources */, 51B54AB324B5AC830014348B /* ArticleButtonState.swift in Sources */, 17D5F17224B0BC6700375168 /* SidebarToolbarModel.swift in Sources */, From 0904ff519f79b435a04b57da426280687f1603e6 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 15 Jul 2020 11:16:16 +0800 Subject: [PATCH 24/28] moves preferredColorScheme to App level. --- Multiplatform/Shared/MainApp.swift | 1 + Multiplatform/macOS/Preferences/MacPreferencesView.swift | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index 0b5a0f893..4ea2bb381 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -79,6 +79,7 @@ struct MainApp: App { .frame(width: 500) .navigationTitle("Preferences") .environmentObject(defaults) + .preferredColorScheme(AppDefaults.userInterfaceColorScheme) } #endif diff --git a/Multiplatform/macOS/Preferences/MacPreferencesView.swift b/Multiplatform/macOS/Preferences/MacPreferencesView.swift index 1bc3cabcb..cb7585b42 100644 --- a/Multiplatform/macOS/Preferences/MacPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/MacPreferencesView.swift @@ -82,7 +82,6 @@ struct MacPreferencesView: View { } } } - .preferredColorScheme(AppDefaults.userInterfaceColorScheme) } } From 995278ac58a35b0f01e5edd2c614e143eef47a07 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Thu, 16 Jul 2020 06:43:11 +0800 Subject: [PATCH 25/28] Enables check for updates --- Multiplatform/macOS/AppDelegate.swift | 2 +- Multiplatform/macOS/Info.plist | 4 ++++ .../Preference Panes/Advanced/AdvancedPreferencesView.swift | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Multiplatform/macOS/AppDelegate.swift b/Multiplatform/macOS/AppDelegate.swift index 2ad541197..cd4e7df12 100644 --- a/Multiplatform/macOS/AppDelegate.swift +++ b/Multiplatform/macOS/AppDelegate.swift @@ -65,7 +65,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele private let appNewsURLString = "https://nnw.ranchero.com/feed.json" private let appMovementMonitor = RSAppMovementMonitor() #if !MAC_APP_STORE && !TEST - private var softwareUpdater: SPUUpdater! + var softwareUpdater: SPUUpdater! #endif override init() { diff --git a/Multiplatform/macOS/Info.plist b/Multiplatform/macOS/Info.plist index 0d9d0860c..ec33c4818 100644 --- a/Multiplatform/macOS/Info.plist +++ b/Multiplatform/macOS/Info.plist @@ -49,5 +49,9 @@ + SUFeedURL + https://ranchero.com/downloads/netnewswire-5.1-beta.xml + FeedURLForTestBuilds + https://ranchero.com/downloads/netnewswire-5.1-beta.xml diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Advanced/AdvancedPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Advanced/AdvancedPreferencesView.swift index 800514cf0..35f0dbaf2 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/Advanced/AdvancedPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/Advanced/AdvancedPreferencesView.swift @@ -25,7 +25,9 @@ struct AdvancedPreferencesView: View { HStack { Spacer() - Button("Check for Updates", action: {}) + Button("Check for Updates") { + appDelegate.softwareUpdater.checkForUpdates() + } Spacer() }.padding(.bottom, 8) From 1b5b219e542845066babe8406f1c3b7353d27a75 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Thu, 16 Jul 2020 06:49:01 +0800 Subject: [PATCH 26/28] Restores storyboard to 36b4907e --- Mac/Base.lproj/Preferences.storyboard | 89 +++++++++++++-------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/Mac/Base.lproj/Preferences.storyboard b/Mac/Base.lproj/Preferences.storyboard index acc9e9694..0c90be9b1 100644 --- a/Mac/Base.lproj/Preferences.storyboard +++ b/Mac/Base.lproj/Preferences.storyboard @@ -1,8 +1,7 @@ - + - - + @@ -32,14 +31,14 @@ - + - + - + @@ -47,13 +46,13 @@ - + - + @@ -82,7 +81,7 @@ - + @@ -90,10 +89,10 @@ - + - + @@ -105,7 +104,7 @@ - + @@ -133,7 +132,7 @@ - + - + - + - + @@ -177,7 +176,7 @@ - + @@ -243,14 +242,14 @@ - + - + - + @@ -258,7 +257,7 @@