diff --git a/Multiplatform/iOS/Settings/Accounts/AccountCredentialsError.swift b/Multiplatform/iOS/Settings/Accounts/AccountCredentialsError.swift new file mode 100644 index 000000000..87638aa23 --- /dev/null +++ b/Multiplatform/iOS/Settings/Accounts/AccountCredentialsError.swift @@ -0,0 +1,37 @@ +// +// AccountCredentialsError.swift +// Multiplatform iOS +// +// Created by Rizwan on 21/07/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation + +enum AccountCredentialsError: CustomStringConvertible, Equatable { + case none, keyChain, invalidCredentials, noNetwork, other(error: Error) + + var description: String { + switch self { + case .keyChain: + return NSLocalizedString("Keychain error while storing credentials.", comment: "") + case .invalidCredentials: + return NSLocalizedString("Invalid email/password combination.", comment: "") + case .noNetwork: + return NSLocalizedString("Network error. Try again later.", comment: "") + case .other(let error): + return NSLocalizedString(error.localizedDescription, comment: "Other add account error") + default: + return "" + } + } + + static func ==(lhs: AccountCredentialsError, rhs: AccountCredentialsError) -> Bool { + switch (lhs, rhs) { + case (.other(let lhsError), .other(let rhsError)): + return lhsError.localizedDescription == rhsError.localizedDescription + default: + return false + } + } +} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsAddAccountView.swift b/Multiplatform/iOS/Settings/Accounts/SettingsAddAccountView.swift index 5234e0f5b..7f7e858a2 100644 --- a/Multiplatform/iOS/Settings/Accounts/SettingsAddAccountView.swift +++ b/Multiplatform/iOS/Settings/Accounts/SettingsAddAccountView.swift @@ -27,17 +27,13 @@ struct SettingsAddAccountView: View { } .listStyle(InsetGroupedListStyle()) .sheet(isPresented: $model.isAddPresented) { - switch model.selectedAccountType { case .onMyMac: SettingsLocalAccountView() - - case .feedbin: - SettingsFeedbinAccountView() - + case .feedbin, .feedWrangler, .newsBlur: + SettingsCredentialsAccountView(accountType: model.selectedAccountType!) case .cloudKit: SettingsCloudKitAccountView() - default: EmptyView() } diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountModel.swift b/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountModel.swift new file mode 100644 index 000000000..96adbab0f --- /dev/null +++ b/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountModel.swift @@ -0,0 +1,224 @@ +// +// SettingsCredentialsAccountModel.swift +// Multiplatform iOS +// +// Created by Rizwan on 21/07/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import Account +import Secrets + +class SettingsCredentialsAccountModel: ObservableObject { + var account: Account? = nil + var accountType: AccountType + @Published var shouldDismiss: Bool = false + @Published var email: String = "" + @Published var password: String = "" + @Published var busy: Bool = false + @Published var accountCredentialsError: AccountCredentialsError? { + didSet { + accountCredentialsError != AccountCredentialsError.none ? (showError = true) : (showError = false) + } + } + @Published var showError: Bool = false + @Published var showPassword: Bool = false + + init(account: Account) { + self.account = account + self.accountType = account.type + if let credentials = try? account.retrieveCredentials(type: .basic) { + self.email = credentials.username + self.password = credentials.secret + } + } + + init(accountType: AccountType) { + self.accountType = accountType + } + + var isUpdate: Bool { + return account != nil + } + + var isValid: Bool { + return !email.isEmpty && !password.isEmpty + } + + var accountName: String { + switch accountType { + case .onMyMac: + return Account.defaultLocalAccountName + case .cloudKit: + return "iCloud" + case .feedbin: + return "Feedbin" + case .feedly: + return "Feedly" + case .feedWrangler: + return "Feed Wrangler" + case .newsBlur: + return "NewsBlur" + default: + return "" + } + } + + var emailText: String { + return accountType == .newsBlur ? NSLocalizedString("Username or Email", comment: "") : NSLocalizedString("Email", comment: "") + } + + func addAccount() { + switch accountType { + case .feedbin: + addFeedbinAccount() + case .feedWrangler: + addFeedWranglerAccount() + case .newsBlur: + addNewsBlurAccount() + default: + return + } + } +} + +extension SettingsCredentialsAccountModel { + // MARK:- Feedbin + + func addFeedbinAccount() { + busy = true + accountCredentialsError = AccountCredentialsError.none + + let emailAddress = email.trimmingCharacters(in: .whitespaces) + let credentials = Credentials(type: .basic, username: emailAddress, secret: password) + + Account.validateCredentials(type: .feedbin, credentials: credentials) { (result) in + self.busy = false + + switch result { + case .success(let authenticated): + if (authenticated != nil) { + var newAccount = false + let workAccount: Account + if self.account == nil { + workAccount = AccountManager.shared.createAccount(type: .feedbin) + newAccount = true + } else { + workAccount = self.account! + } + + do { + do { + try workAccount.removeCredentials(type: .basic) + } catch {} + try workAccount.storeCredentials(credentials) + + if newAccount { + workAccount.refreshAll() { result in } + } + + self.shouldDismiss = true + } catch { + self.accountCredentialsError = AccountCredentialsError.keyChain + } + + } else { + self.accountCredentialsError = AccountCredentialsError.invalidCredentials + } + case .failure: + self.accountCredentialsError = AccountCredentialsError.noNetwork + } + } + } + + // MARK: FeedWrangler + + func addFeedWranglerAccount() { + busy = true + let credentials = Credentials(type: .feedWranglerBasic, username: email, secret: password) + + Account.validateCredentials(type: .feedWrangler, credentials: credentials) { [weak self] result in + guard let self = self else { return } + + self.busy = false + switch result { + case .success(let validatedCredentials): + guard let validatedCredentials = validatedCredentials else { + self.accountCredentialsError = .invalidCredentials + 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) + self.shouldDismiss = true + account.refreshAll(completion: { result in + switch result { + case .success: + break + case .failure(let error): + self.accountCredentialsError = .other(error: error) + } + }) + + } catch { + self.accountCredentialsError = .keyChain + } + + case .failure: + self.accountCredentialsError = .noNetwork + } + } + } + + // MARK:- NewsBlur + + func addNewsBlurAccount() { + busy = true + let credentials = Credentials(type: .newsBlurBasic, username: email, secret: password) + + Account.validateCredentials(type: .newsBlur, credentials: credentials) { [weak self] result in + + guard let self = self else { return } + + self.busy = false + + switch result { + case .success(let validatedCredentials): + + guard let validatedCredentials = validatedCredentials else { + self.accountCredentialsError = .invalidCredentials + 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) + self.shouldDismiss = true + account.refreshAll(completion: { result in + switch result { + case .success: + break + case .failure(let error): + self.accountCredentialsError = .other(error: error) + } + }) + + } catch { + self.accountCredentialsError = .keyChain + } + + case .failure: + self.accountCredentialsError = .noNetwork + } + } + } +} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsFeedbinAccountView.swift b/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountView.swift similarity index 63% rename from Multiplatform/iOS/Settings/Accounts/SettingsFeedbinAccountView.swift rename to Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountView.swift index 27bd58b3d..373705075 100644 --- a/Multiplatform/iOS/Settings/Accounts/SettingsFeedbinAccountView.swift +++ b/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountView.swift @@ -1,35 +1,31 @@ // -// SettingsFeedbinAccountView.swift +// SettingsCredentialsAccountView.swift // Multiplatform iOS // -// Created by Rizwan on 07/07/20. +// Created by Rizwan on 21/07/20. // Copyright © 2020 Ranchero Software. All rights reserved. // import SwiftUI import Account -import Combine -import RSWeb -import Secrets -struct SettingsFeedbinAccountView: View { +struct SettingsCredentialsAccountView: View { @Environment(\.presentationMode) var presentationMode - @ObservedObject var settingsModel: SettingsFeedbinAccountModel + @ObservedObject var settingsModel: SettingsCredentialsAccountModel - init(account: Account? = nil) { - if let account = account { - self.settingsModel = SettingsFeedbinAccountModel(account: account) - } - else { - self.settingsModel = SettingsFeedbinAccountModel() - } + init(account: Account) { + self.settingsModel = SettingsCredentialsAccountModel(account: account) + } + + init(accountType: AccountType) { + self.settingsModel = SettingsCredentialsAccountModel(accountType: accountType) } var body: some View { NavigationView { List { - Section(header: AccountHeaderImageView(image: AppAssets.image(for: .feedbin)!)) { - TextField("Email", text: $settingsModel.email).textContentType(.emailAddress) + Section(header: AccountHeaderImageView(image: AppAssets.image(for: settingsModel.accountType)!)) { + TextField(settingsModel.emailText, text: $settingsModel.email).textContentType(.emailAddress) HStack { if settingsModel.showPassword { TextField("Password", text:$settingsModel.password) @@ -69,7 +65,7 @@ struct SettingsFeedbinAccountView: View { presentationMode.wrappedValue.dismiss() } }) - .navigationBarTitle(Text(verbatim: "Feedbin"), displayMode: .inline) + .navigationBarTitle(Text(verbatim: settingsModel.accountName), displayMode: .inline) .navigationBarItems(leading: Button(action: { self.dismiss() }) { Text("Cancel") } ) @@ -80,7 +76,7 @@ struct SettingsFeedbinAccountView: View { HStack { Spacer() if settingsModel.showError { - Text(verbatim: settingsModel.feedbinAccountError!.localizedDescription).foregroundColor(.red) + Text(verbatim: settingsModel.accountCredentialsError!.description).foregroundColor(.red) } Spacer() } @@ -91,8 +87,8 @@ struct SettingsFeedbinAccountView: View { } } -struct SettingsFeedbinAccountView_Previews: PreviewProvider { - static var previews: some View { - SettingsFeedbinAccountView() - } +struct SettingsCredentialsAccountView_Previews: PreviewProvider { + static var previews: some View { + SettingsCredentialsAccountView(accountType: .feedbin) + } } diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsDetailAccountView.swift b/Multiplatform/iOS/Settings/Accounts/SettingsDetailAccountView.swift index c28c787e6..0e2850526 100644 --- a/Multiplatform/iOS/Settings/Accounts/SettingsDetailAccountView.swift +++ b/Multiplatform/iOS/Settings/Accounts/SettingsDetailAccountView.swift @@ -45,7 +45,7 @@ struct SettingsDetailAccountView: View { } } .sheet(isPresented: $isFeedbinCredentialsPresented) { - self.settingsFeedbinAccountView + self.settingsCredentialsAccountView } } if settingsModel.isDeletable { @@ -78,8 +78,8 @@ struct SettingsDetailAccountView: View { .navigationBarTitle(Text(verbatim: settingsModel.nameForDisplay), displayMode: .inline) } - var settingsFeedbinAccountView: SettingsFeedbinAccountView { - return SettingsFeedbinAccountView(account: settingsModel.account) + var settingsCredentialsAccountView: SettingsCredentialsAccountView { + return SettingsCredentialsAccountView(account: settingsModel.account) } func dismiss() { diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsFeedbinAccountModel.swift b/Multiplatform/iOS/Settings/Accounts/SettingsFeedbinAccountModel.swift deleted file mode 100644 index c3e4091c2..000000000 --- a/Multiplatform/iOS/Settings/Accounts/SettingsFeedbinAccountModel.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// SettingsFeedbinAccountModel.swift -// Multiplatform iOS -// -// Created by Rizwan on 08/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import Secrets - -enum FeedbinAccountError: LocalizedError { - - case none, keyChain, invalidCredentials, noNetwork - - var errorDescription: String? { - switch self { - case .keyChain: - return NSLocalizedString("Keychain error while storing credentials.", comment: "") - case .invalidCredentials: - return NSLocalizedString("Invalid email/password combination.", comment: "") - case .noNetwork: - return NSLocalizedString("Network error. Try again later.", comment: "") - default: - return nil - } - } - -} - -class SettingsFeedbinAccountModel: ObservableObject { - var account: Account? = nil - @Published var shouldDismiss: Bool = false - @Published var email: String = "" - @Published var password: String = "" - @Published var busy: Bool = false - @Published var feedbinAccountError: FeedbinAccountError? { - didSet { - feedbinAccountError != FeedbinAccountError.none ? (showError = true) : (showError = false) - } - } - @Published var showError: Bool = false - @Published var showPassword: Bool = false - - init() { - - } - - init(account: Account) { - self.account = account - if let credentials = try? account.retrieveCredentials(type: .basic) { - self.email = credentials.username - self.password = credentials.secret - } - } - - var isUpdate: Bool { - return account != nil - } - - var isValid: Bool { - return !email.isEmpty && !password.isEmpty - } - - func addAccount() { - busy = true - feedbinAccountError = FeedbinAccountError.none - - let emailAddress = email.trimmingCharacters(in: .whitespaces) - let credentials = Credentials(type: .basic, username: emailAddress, secret: password) - - Account.validateCredentials(type: .feedbin, credentials: credentials) { (result) in - self.busy = false - - switch result { - case .success(let authenticated): - if (authenticated != nil) { - var newAccount = false - let workAccount: Account - if self.account == nil { - workAccount = AccountManager.shared.createAccount(type: .feedbin) - newAccount = true - } else { - workAccount = self.account! - } - - do { - do { - try workAccount.removeCredentials(type: .basic) - } catch {} - try workAccount.storeCredentials(credentials) - - if newAccount { - workAccount.refreshAll() { result in } - } - - self.shouldDismiss = true - } catch { - self.feedbinAccountError = FeedbinAccountError.keyChain - } - - } else { - self.feedbinAccountError = FeedbinAccountError.invalidCredentials - } - case .failure: - self.feedbinAccountError = FeedbinAccountError.noNetwork - } - } - } -} - diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index f8ad4b4e8..52ee74c52 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -706,6 +706,9 @@ 55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */; }; 55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; }; 5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F323808231DF9F000706F6B /* VibrantTableViewCell.swift */; }; + 65082A2F24C72AC8009FA994 /* SettingsCredentialsAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65082A2E24C72AC8009FA994 /* SettingsCredentialsAccountView.swift */; }; + 65082A5224C72B88009FA994 /* SettingsCredentialsAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65082A5124C72B88009FA994 /* SettingsCredentialsAccountModel.swift */; }; + 65082A5424C73D2F009FA994 /* AccountCredentialsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65082A5324C73D2F009FA994 /* AccountCredentialsError.swift */; }; 653A4E7924BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A4E7824BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift */; }; 65422D1724B75CD1008A2FA2 /* SettingsAddAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65422D1624B75CD1008A2FA2 /* SettingsAddAccountModel.swift */; }; 6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; }; @@ -714,14 +717,12 @@ 6581C74020CED60100F4AD34 /* netnewswire-subscribe-to-feed.js in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */; }; 6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; }; 6586A5F724B632F8002BCF4F /* SettingsDetailAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6586A5F624B632F8002BCF4F /* SettingsDetailAccountModel.swift */; }; - 6591720E24B59C5100B638E8 /* SettingsFeedbinAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6591720D24B59C5100B638E8 /* SettingsFeedbinAccountModel.swift */; }; 6591723124B5C35400B638E8 /* AccountHeaderImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6591723024B5C35400B638E8 /* AccountHeaderImageView.swift */; }; 6591727F24B5D19500B638E8 /* SettingsDetailAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6591727E24B5D19500B638E8 /* SettingsDetailAccountView.swift */; }; 6594CA3B24AF6F2A005C7D7C /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; }; 65ACE48424B4779B003AE06A /* SettingsAddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65ACE48324B4779B003AE06A /* SettingsAddAccountView.swift */; }; 65ACE48624B477C9003AE06A /* SettingsAccountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65ACE48524B477C9003AE06A /* SettingsAccountLabelView.swift */; }; 65ACE48824B48020003AE06A /* SettingsLocalAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65ACE48724B48020003AE06A /* SettingsLocalAccountView.swift */; }; - 65ACE48A24B4C2D8003AE06A /* SettingsFeedbinAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65ACE48924B4C2D8003AE06A /* SettingsFeedbinAccountView.swift */; }; 65C2E40124B05D8A000AFDF6 /* FeedsSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65C2E40024B05D8A000AFDF6 /* FeedsSettingsModel.swift */; }; 65CBAD5A24AE03C20006DD91 /* ColorPaletteContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65CBAD5924AE03C20006DD91 /* ColorPaletteContainerView.swift */; }; 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; @@ -2147,6 +2148,9 @@ 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsReaderAPI.xib; sourceTree = ""; }; 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsReaderAPIWindowController.swift; sourceTree = ""; }; 5F323808231DF9F000706F6B /* VibrantTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantTableViewCell.swift; sourceTree = ""; }; + 65082A2E24C72AC8009FA994 /* SettingsCredentialsAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCredentialsAccountView.swift; sourceTree = ""; }; + 65082A5124C72B88009FA994 /* SettingsCredentialsAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCredentialsAccountModel.swift; sourceTree = ""; }; + 65082A5324C73D2F009FA994 /* AccountCredentialsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCredentialsError.swift; sourceTree = ""; }; 653A4E7824BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCloudKitAccountView.swift; sourceTree = ""; }; 65422D1624B75CD1008A2FA2 /* SettingsAddAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAddAccountModel.swift; sourceTree = ""; }; 6543108B2322D90900658221 /* common */ = {isa = PBXFileReference; lastKnownFileType = folder; path = common; sourceTree = ""; }; @@ -2160,13 +2164,11 @@ 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = ToolbarItemIcon.pdf; sourceTree = ""; }; 6581C74320CED60100F4AD34 /* Subscribe_to_Feed.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Subscribe_to_Feed.entitlements; sourceTree = ""; }; 6586A5F624B632F8002BCF4F /* SettingsDetailAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDetailAccountModel.swift; sourceTree = ""; }; - 6591720D24B59C5100B638E8 /* SettingsFeedbinAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeedbinAccountModel.swift; sourceTree = ""; }; 6591723024B5C35400B638E8 /* AccountHeaderImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountHeaderImageView.swift; sourceTree = ""; }; 6591727E24B5D19500B638E8 /* SettingsDetailAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDetailAccountView.swift; sourceTree = ""; }; 65ACE48324B4779B003AE06A /* SettingsAddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAddAccountView.swift; sourceTree = ""; }; 65ACE48524B477C9003AE06A /* SettingsAccountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountLabelView.swift; sourceTree = ""; }; 65ACE48724B48020003AE06A /* SettingsLocalAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLocalAccountView.swift; sourceTree = ""; }; - 65ACE48924B4C2D8003AE06A /* SettingsFeedbinAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeedbinAccountView.swift; sourceTree = ""; }; 65C2E40024B05D8A000AFDF6 /* FeedsSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedsSettingsModel.swift; sourceTree = ""; }; 65CBAD5924AE03C20006DD91 /* ColorPaletteContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPaletteContainerView.swift; sourceTree = ""; }; 65ED4083235DEF6C0081F399 /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -3312,12 +3314,13 @@ 65422D1624B75CD1008A2FA2 /* SettingsAddAccountModel.swift */, 65ACE48524B477C9003AE06A /* SettingsAccountLabelView.swift */, 65ACE48724B48020003AE06A /* SettingsLocalAccountView.swift */, - 65ACE48924B4C2D8003AE06A /* SettingsFeedbinAccountView.swift */, - 6591720D24B59C5100B638E8 /* SettingsFeedbinAccountModel.swift */, 6591723024B5C35400B638E8 /* AccountHeaderImageView.swift */, 6591727E24B5D19500B638E8 /* SettingsDetailAccountView.swift */, 6586A5F624B632F8002BCF4F /* SettingsDetailAccountModel.swift */, 653A4E7824BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift */, + 65082A2E24C72AC8009FA994 /* SettingsCredentialsAccountView.swift */, + 65082A5124C72B88009FA994 /* SettingsCredentialsAccountModel.swift */, + 65082A5324C73D2F009FA994 /* AccountCredentialsError.swift */, ); path = Accounts; sourceTree = ""; @@ -5161,7 +5164,6 @@ 5177471824B3812200EB0F74 /* IconView.swift in Sources */, 51E4995C24A875F300B667CB /* ArticleRenderer.swift in Sources */, 51E4992324A8095700B667CB /* URL-Extensions.swift in Sources */, - 65ACE48A24B4C2D8003AE06A /* SettingsFeedbinAccountView.swift in Sources */, 51E4993624A867E800B667CB /* UserInfoKey.swift in Sources */, 51E4990924A808C500B667CB /* WebFeedIconDownloader.swift in Sources */, 51E498F524A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, @@ -5169,6 +5171,7 @@ 172199F124AB716900A31D04 /* SidebarToolbarModifier.swift in Sources */, 65CBAD5A24AE03C20006DD91 /* ColorPaletteContainerView.swift in Sources */, 5177470924B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */, + 65082A5424C73D2F009FA994 /* AccountCredentialsError.swift in Sources */, 51E4990B24A808C500B667CB /* ImageDownloader.swift in Sources */, 51E498F424A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, 514E6BFF24AD255D00AC6F6E /* PreviewArticles.swift in Sources */, @@ -5192,11 +5195,12 @@ 51E49A0324A91FF600B667CB /* SceneNavigationView.swift in Sources */, 51E4990124A808BB00B667CB /* FaviconURLFinder.swift in Sources */, 51E4991D24A8092100B667CB /* NSAttributedString+NetNewsWire.swift in Sources */, - 6591720E24B59C5100B638E8 /* SettingsFeedbinAccountModel.swift in Sources */, + 65082A2F24C72AC8009FA994 /* SettingsCredentialsAccountView.swift in Sources */, 51E499FD24A9137600B667CB /* SidebarModel.swift in Sources */, 5181C66224B0C326002E0F70 /* SettingsModel.swift in Sources */, 51E4995324A8734D00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, 5177471024B3029400EB0F74 /* ArticleViewController.swift in Sources */, + 65082A5224C72B88009FA994 /* SettingsCredentialsAccountModel.swift in Sources */, 172199C924AB228900A31D04 /* SettingsView.swift in Sources */, 51B8BCC224C25C3E00360B00 /* SidebarContextMenu.swift in Sources */, 17D232A824AFF10A0005F075 /* AddWebFeedModel.swift in Sources */,