From 3fdb0d1f0429786cf784f8b84a48438e6279f4c3 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 28 Oct 2020 23:19:42 +0800 Subject: [PATCH] Prototype Refresh --- Mac/Preferences/Accounts/AccountsAdd.xib | 12 +- .../Accounts/AccountsAddViewController.swift | 1 - .../AccountsPreferencesViewController.swift | 117 ++++++++- .../Accounts/AddAccountsView.swift | 241 ++++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 6 + .../xcschemes/NetNewsWire.xcscheme | 1 + 6 files changed, 367 insertions(+), 11 deletions(-) create mode 100644 Mac/Preferences/Accounts/AddAccountsView.swift diff --git a/Mac/Preferences/Accounts/AccountsAdd.xib b/Mac/Preferences/Accounts/AccountsAdd.xib index 81789f03b..1fb72c35e 100644 --- a/Mac/Preferences/Accounts/AccountsAdd.xib +++ b/Mac/Preferences/Accounts/AccountsAdd.xib @@ -1,8 +1,8 @@ - + - + @@ -50,7 +50,7 @@ - + @@ -61,9 +61,9 @@ - + - + @@ -83,7 +83,7 @@ - + diff --git a/Mac/Preferences/Accounts/AccountsAddViewController.swift b/Mac/Preferences/Accounts/AccountsAddViewController.swift index c541160f0..2b78d7532 100644 --- a/Mac/Preferences/Accounts/AccountsAddViewController.swift +++ b/Mac/Preferences/Accounts/AccountsAddViewController.swift @@ -171,7 +171,6 @@ extension AccountsAddViewController: AccountsAddTableCellViewDelegate { accountsReaderAPIWindowController.accountType = .theOldReader accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) accountsAddWindowController = accountsReaderAPIWindowController - } } diff --git a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift index 740e77f5a..7e08f3cd7 100644 --- a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift +++ b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift @@ -8,12 +8,17 @@ import AppKit import Account +import SwiftUI +import RSCore + final class AccountsPreferencesViewController: NSViewController { @IBOutlet weak var tableView: NSTableView! @IBOutlet weak var detailView: NSView! @IBOutlet weak var deleteButton: NSButton! + var addAccountDelegate: AccountsPreferencesAddAccountDelegate? + private var sortedAccounts = [Account]() @@ -23,12 +28,13 @@ final class AccountsPreferencesViewController: NSViewController { updateSortedAccounts() tableView.delegate = self tableView.dataSource = self + addAccountDelegate = self NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .UserDidAddAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .UserDidDeleteAccount, object: nil) - showController(AccountsAddViewController()) + //showController(AccountsAddViewController()) // Fix tableView frame — for some reason IB wants it 1pt wider than the clip view. This leads to unwanted horizontal scrolling. var rTable = tableView.frame @@ -37,8 +43,10 @@ final class AccountsPreferencesViewController: NSViewController { } @IBAction func addAccount(_ sender: Any) { - tableView.selectRowIndexes([], byExtendingSelection: false) - showController(AccountsAddViewController()) + //tableView.selectRowIndexes([], byExtendingSelection: false) + let controller = NSHostingController(rootView: AddAccountsView(delegate: self)) + controller.rootView.parent = controller + presentAsSheet(controller) } @IBAction func removeAccount(_ sender: Any) { @@ -62,7 +70,7 @@ final class AccountsPreferencesViewController: NSViewController { if result == NSApplication.ModalResponse.alertFirstButtonReturn { guard let self = self else { return } AccountManager.shared.deleteAccount(self.sortedAccounts[self.tableView.selectedRow]) - self.showController(AccountsAddViewController()) + //self.showController(AccountsAddViewController()) } } @@ -132,6 +140,106 @@ extension AccountsPreferencesViewController: NSTableViewDelegate { } +// MARK: - AccountsPreferencesAddAccountDelegate +protocol AccountsPreferencesAddAccountDelegate { + func presentSheetForAccount(_ accountType: AccountType) +} + +extension AccountsPreferencesViewController: AccountsPreferencesAddAccountDelegate { + func presentSheetForAccount(_ accountType: AccountType) { + switch accountType { + case .onMyMac: + let accountsAddLocalWindowController = AccountsAddLocalWindowController() + accountsAddLocalWindowController.runSheetOnWindow(self.view.window!) + //accountsAddWindowController = accountsAddLocalWindowController + + case .cloudKit: + let accountsAddCloudKitWindowController = AccountsAddCloudKitWindowController() + accountsAddCloudKitWindowController.runSheetOnWindow(self.view.window!) { response in + if response == NSApplication.ModalResponse.OK { + //self.restrictAccounts() + //self.tableView.reloadData() + } + } + //accountsAddWindowController = accountsAddCloudKitWindowController + + case .feedbin: + let accountsFeedbinWindowController = AccountsFeedbinWindowController() + accountsFeedbinWindowController.runSheetOnWindow(self.view.window!) + //accountsAddWindowController = accountsFeedbinWindowController + + case .feedWrangler: + let accountsFeedWranglerWindowController = AccountsFeedWranglerWindowController() + accountsFeedWranglerWindowController.runSheetOnWindow(self.view.window!) + //accountsAddWindowController = accountsFeedWranglerWindowController + + case .freshRSS: + let accountsReaderAPIWindowController = AccountsReaderAPIWindowController() + accountsReaderAPIWindowController.accountType = .freshRSS + accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) + //accountsAddWindowController = accountsReaderAPIWindowController + + case .feedly: + let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly) + //addAccount.delegate = self + addAccount.presentationAnchor = self.view.window! + runAwaitingFeedlyLoginAlertModal(forLifetimeOf: addAccount) + MainThreadOperationQueue.shared.add(addAccount) + + case .newsBlur: + let accountsNewsBlurWindowController = AccountsNewsBlurWindowController() + accountsNewsBlurWindowController.runSheetOnWindow(self.view.window!) + //accountsAddWindowController = accountsNewsBlurWindowController + + case .inoreader: + let accountsReaderAPIWindowController = AccountsReaderAPIWindowController() + accountsReaderAPIWindowController.accountType = .inoreader + accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) + //accountsAddWindowController = accountsReaderAPIWindowController + + case .bazQux: + let accountsReaderAPIWindowController = AccountsReaderAPIWindowController() + accountsReaderAPIWindowController.accountType = .bazQux + accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) + //accountsAddWindowController = accountsReaderAPIWindowController + + case .theOldReader: + let accountsReaderAPIWindowController = AccountsReaderAPIWindowController() + accountsReaderAPIWindowController.accountType = .theOldReader + accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) + //accountsAddWindowController = accountsReaderAPIWindowController + + } + } + + private func runAwaitingFeedlyLoginAlertModal(forLifetimeOf operation: OAuthAccountAuthorizationOperation) { + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = NSLocalizedString("Waiting for access to Feedly", + comment: "Alert title when adding a Feedly account and waiting for authorization from the user.") + + alert.informativeText = NSLocalizedString("Your default web browser will open the Feedly login for you to authorize access.", + comment: "Alert informative text when adding a Feedly account and waiting for authorization from the user.") + + alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel")) + + let attachedWindow = self.view.window! + + alert.beginSheetModal(for: attachedWindow) { response in + if response == .alertFirstButtonReturn { + operation.cancel() + } + } + + operation.completionBlock = { _ in + guard alert.window.isVisible else { + return + } + attachedWindow.endSheet(alert.window) + } + } +} + // MARK: - Private private extension AccountsPreferencesViewController { @@ -155,3 +263,4 @@ private extension AccountsPreferencesViewController { } } + diff --git a/Mac/Preferences/Accounts/AddAccountsView.swift b/Mac/Preferences/Accounts/AddAccountsView.swift new file mode 100644 index 000000000..c52843f02 --- /dev/null +++ b/Mac/Preferences/Accounts/AddAccountsView.swift @@ -0,0 +1,241 @@ +// +// AddAccountsView.swift +// NetNewsWire +// +// Created by Stuart Breckenridge on 28/10/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account + +private enum AddAccountSections: Int, CaseIterable { + case local = 0 + case icloud + case web + case selfhosted + + var sectionHeader: String { + switch self { + case .local: + return NSLocalizedString("Local", comment: "Local Account") + case .icloud: + return NSLocalizedString("iCloud", comment: "iCloud Account") + case .web: + return NSLocalizedString("Web", comment: "Web Account") + case .selfhosted: + return NSLocalizedString("Self-hosted", comment: "Self hosted Account") + } + } + + var sectionFooter: String { + switch self { + case .local: + return NSLocalizedString("This account does not sync subscriptions across devices.", comment: "Local Account") + case .icloud: + return NSLocalizedString("Use your iCloud account to sync your subscriptions across your iOS and macOS devices.", comment: "iCloud Account") + case .web: + return NSLocalizedString("Web accounts sync your subscriptions across all your devices.", comment: "Web Account") + case .selfhosted: + return NSLocalizedString("Self-hosted accounts sync your subscriptions across all your devices.", comment: "Self hosted Account") + } + } + + var sectionContent: [AccountType] { + switch self { + case .local: + return [.onMyMac] + case .icloud: + return [.cloudKit] + case .web: + return [.bazQux, .feedbin, .feedly, .feedWrangler, .inoreader, .newsBlur, .theOldReader] + case .selfhosted: + return [.freshRSS] + } + } +} + +struct AddAccountsView: View { + + weak var parent: NSHostingController? // required because presentationMode.dismiss() doesn't work + var addAccountDelegate: AccountsPreferencesAddAccountDelegate? + init(delegate: AccountsPreferencesAddAccountDelegate?) { + self.addAccountDelegate = delegate + } + @State private var selectedAccount: AccountType = .onMyMac + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Text("Choose an account type to add...") + .font(.headline) + .padding() + + localAccount + icloudAccount + webAccounts + selfhostedAccounts + + HStack { + Spacer() + if #available(OSX 11.0, *) { + Button(action: { + parent?.dismiss(nil) + }, label: { + Text("Cancel") + .accessibility(label: Text("Add Account")) + }).keyboardShortcut(.cancelAction) + } else { + Button(action: { + parent?.dismiss(nil) + }, label: { + Text("Cancel") + .accessibility(label: Text("Add Account")) + }) + } + if #available(OSX 11.0, *) { + Button(action: { + addAccountDelegate?.presentSheetForAccount(selectedAccount) + parent?.dismiss(nil) + }, label: { + Text("Continue") + }).keyboardShortcut(.defaultAction) + } else { + Button(action: { + addAccountDelegate?.presentSheetForAccount(selectedAccount) + parent?.dismiss(nil) + }, label: { + Text("Continue") + }) + } + }.padding([.top, .bottom], 4) + } + .pickerStyle(RadioGroupPickerStyle()) + .fixedSize(horizontal: false, vertical: true) + .frame(width: 400) + .padding(12) + } + + var localAccount: some View { + VStack(alignment: .leading) { + Text("Local") + .font(.headline) + .padding(.horizontal) + Picker(selection: $selectedAccount, label: Text(""), content: { + ForEach(AddAccountSections.local.sectionContent, id: \.self, content: { account in + HStack(alignment: .top) { + Image(nsImage: AppAssets.image(for: account)!) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 25, height: 25, alignment: .center) + .offset(CGSize(width: 0, height: -2.5)) + + VStack(alignment: .leading, spacing: 4) { + Text(account.localizedAccountName()) + Text(AddAccountSections.local.sectionFooter).foregroundColor(.gray) + .font(.caption) + } + } + .tag(account) + }) + }) + .pickerStyle(RadioGroupPickerStyle()) + } + + } + + var icloudAccount: some View { + VStack(alignment: .leading) { + Text("iCloud") + .font(.headline) + .padding(.horizontal) + Picker(selection: $selectedAccount, label: Text(""), content: { + ForEach(AddAccountSections.icloud.sectionContent, id: \.self, content: { account in + HStack(alignment: .top) { + Image(nsImage: AppAssets.image(for: account)!) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 25, height: 25, alignment: .center) + .offset(CGSize(width: 0, height: -5)) + + VStack(alignment: .leading, spacing: 4) { + Text(account.localizedAccountName()) + Text(AddAccountSections.icloud.sectionFooter).foregroundColor(.gray) + .font(.caption) + } + } + .tag(account) + }) + }) + .disabled(isCloudInUse()) + } + } + + var webAccounts: some View { + VStack(alignment: .leading) { + Text("Web") + .font(.headline) + .padding(.horizontal) + Picker(selection: $selectedAccount, label: Text(""), content: { + ForEach(AddAccountSections.web.sectionContent.filter({ isRestricted($0) != true }), id: \.self, content: { account in + + HStack(alignment: .center) { + Image(nsImage: AppAssets.image(for: account)!) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 25, height: 25, alignment: .center) + + VStack(alignment: .leading) { + Text(account.localizedAccountName()) + } + } + .tag(account) + + }) + }) + } + } + + var selfhostedAccounts: some View { + VStack(alignment: .leading) { + Text("Self-hosted") + .font(.headline) + .padding(.horizontal) + Picker(selection: $selectedAccount, label: Text(""), content: { + ForEach(AddAccountSections.selfhosted.sectionContent, id: \.self, content: { account in + HStack { + Image(nsImage: AppAssets.image(for: account)!) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 25, height: 25, alignment: .center) + + Text(account.localizedAccountName()) + }.tag(account) + }) + }) + + Text("Web and self-hosted accounts sync across all signed-in devices.") + .font(.caption) + .padding(.leading, 62) + .foregroundColor(.gray) + } + } + + private func isCloudInUse() -> Bool { + AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit }) + } + + private func isRestricted(_ accountType: AccountType) -> Bool { + if AppDefaults.shared.isDeveloperBuild && (accountType == .feedly || accountType == .feedWrangler || accountType == .inoreader) { + return true + } + return false + } +} + + +struct AddAccountsView_Previews: PreviewProvider { + static var previews: some View { + AddAccountsView(delegate: nil) + } +} + diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 5934518be..2d8fcc1a4 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 177A0C2D25454AAB00D7EAF6 /* ReaderAPIAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 177A0C2C25454AAB00D7EAF6 /* ReaderAPIAccountViewController.swift */; }; 17897ACA24C281A40014BA03 /* InspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17897AC924C281A40014BA03 /* InspectorView.swift */; }; 17897ACB24C281A40014BA03 /* InspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17897AC924C281A40014BA03 /* InspectorView.swift */; }; + 178A9F9D2549449F00AB7E9D /* AddAccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 178A9F9C2549449F00AB7E9D /* AddAccountsView.swift */; }; + 178A9F9E2549449F00AB7E9D /* AddAccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 178A9F9C2549449F00AB7E9D /* AddAccountsView.swift */; }; 17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; }; 17930ED524AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; }; 1799E6A924C2F93F00511E91 /* InspectorPlatformModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1799E6A824C2F93F00511E91 /* InspectorPlatformModifier.swift */; }; @@ -1468,6 +1470,7 @@ 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; 177A0C2C25454AAB00D7EAF6 /* ReaderAPIAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderAPIAccountViewController.swift; sourceTree = ""; }; 17897AC924C281A40014BA03 /* InspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorView.swift; sourceTree = ""; }; + 178A9F9C2549449F00AB7E9D /* AddAccountsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountsView.swift; sourceTree = ""; }; 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.swift; sourceTree = ""; }; 1799E6A824C2F93F00511E91 /* InspectorPlatformModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorPlatformModifier.swift; sourceTree = ""; }; 1799E6CC24C320D600511E91 /* InspectorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorModel.swift; sourceTree = ""; }; @@ -3345,6 +3348,7 @@ 84C9FC6F22629E1200D921D6 /* Accounts */ = { isa = PBXGroup; children = ( + 178A9F9C2549449F00AB7E9D /* AddAccountsView.swift */, 84C9FC7222629E1200D921D6 /* AccountsPreferencesViewController.swift */, 51EF0F8D2279C9260050506E /* AccountsAdd.xib */, 51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */, @@ -4948,6 +4952,7 @@ 515A5108243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */, 65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */, 65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */, + 178A9F9E2549449F00AB7E9D /* AddAccountsView.swift in Sources */, 65ED402D235DEF6C0081F399 /* DetailStatusBarView.swift in Sources */, 65ED402E235DEF6C0081F399 /* MainWindowController+Scriptability.swift in Sources */, 65ED402F235DEF6C0081F399 /* PreferencesWindowController.swift in Sources */, @@ -5309,6 +5314,7 @@ 84B99C9D1FAE83C600ECDEDB /* DeleteCommand.swift in Sources */, 849A97541ED9EAC0007D329B /* AddWebFeedWindowController.swift in Sources */, 5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */, + 178A9F9D2549449F00AB7E9D /* AddAccountsView.swift in Sources */, 51C4CFF024D37D1F00AF9874 /* Secrets.swift in Sources */, 849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */, 51333D1624685D2E00EB5C91 /* AddRedditFeedWindowController.swift in Sources */, diff --git a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire.xcscheme b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire.xcscheme index 5cc61ce43..b1d7d2d16 100644 --- a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire.xcscheme +++ b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire.xcscheme @@ -72,6 +72,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + debugAsWhichUser = "root" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO"