From 125ea59cd6a271ff1e8f45e41b857dd712921594 Mon Sep 17 00:00:00 2001
From: Jeremy Beker <gothmog@confusticate.com>
Date: Sun, 16 Jun 2019 18:22:00 -0400
Subject: [PATCH] Wiring up iOS add account settings

---
 ...ogleReaderCompatibleWindowController.swift |   2 +-
 NetNewsWire.xcodeproj/project.pbxproj         |   4 +
 iOS/Settings/SettingsAddAccountView.swift     |   3 +
 iOS/Settings/SettingsFeedbinAccountView.swift |   2 +-
 ...ngsGoogleReaderCompatibleAccountView.swift | 187 ++++++++++++++++++
 .../UIKit/FeedbinAccountViewController.swift  |   2 +-
 6 files changed, 197 insertions(+), 3 deletions(-)
 create mode 100644 iOS/Settings/SettingsGoogleReaderCompatibleAccountView.swift

diff --git a/Mac/Preferences/Accounts/AccountsGoogleReaderCompatibleWindowController.swift b/Mac/Preferences/Accounts/AccountsGoogleReaderCompatibleWindowController.swift
index 89db9f277..b70b481b1 100644
--- a/Mac/Preferences/Accounts/AccountsGoogleReaderCompatibleWindowController.swift
+++ b/Mac/Preferences/Accounts/AccountsGoogleReaderCompatibleWindowController.swift
@@ -66,7 +66,7 @@ class AccountsGoogleReaderCompatibleWindowController: NSWindowController {
 		progressIndicator.startAnimation(self)
 		
 		guard let apiURL = URL(string: apiURLTextField.stringValue) else {
-			self.errorMessageLabel.stringValue = NSLocalizedString("Invalie API URL.", comment: "Credentials Error")
+			self.errorMessageLabel.stringValue = NSLocalizedString("Invalid API URL.", comment: "Credentials Error")
 			return
 		}
 		
diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj
index 414f74d58..09871e8cd 100644
--- a/NetNewsWire.xcodeproj/project.pbxproj
+++ b/NetNewsWire.xcodeproj/project.pbxproj
@@ -154,6 +154,7 @@
 		51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */; };
 		51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BFA2275D85000C787DC /* Array-Extensions.swift */; };
 		51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */; };
+		557EE1AE22B6F4E1004206FA /* SettingsGoogleReaderCompatibleAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557EE1A522B6F4E1004206FA /* SettingsGoogleReaderCompatibleAccountView.swift */; };
 		55E15BCB229D65A900D6602A /* AccountsGoogleReaderCompatible.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55E15BC1229D65A900D6602A /* AccountsGoogleReaderCompatible.xib */; };
 		55E15BCC229D65A900D6602A /* AccountsGoogleReaderCompatibleWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsGoogleReaderCompatibleWindowController.swift */; };
 		6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; };
@@ -750,6 +751,7 @@
 		51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem-Extensions.swift"; sourceTree = "<group>"; };
 		51F85BFA2275D85000C787DC /* Array-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array-Extensions.swift"; sourceTree = "<group>"; };
 		51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLineUILabelSizer.swift; sourceTree = "<group>"; };
+		557EE1A522B6F4E1004206FA /* SettingsGoogleReaderCompatibleAccountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsGoogleReaderCompatibleAccountView.swift; sourceTree = "<group>"; };
 		55E15BC1229D65A900D6602A /* AccountsGoogleReaderCompatible.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsGoogleReaderCompatible.xib; sourceTree = "<group>"; };
 		55E15BCA229D65A900D6602A /* AccountsGoogleReaderCompatibleWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsGoogleReaderCompatibleWindowController.swift; sourceTree = "<group>"; };
 		6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1050,6 +1052,7 @@
 		5183CCEB227117C70010922C /* Settings */ = {
 			isa = PBXGroup;
 			children = (
+				557EE1A522B6F4E1004206FA /* SettingsGoogleReaderCompatibleAccountView.swift */,
 				510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */,
 				510D707322B028E1004E8F65 /* SettingsAddAccountView.swift */,
 				51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */,
@@ -2375,6 +2378,7 @@
 				51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */,
 				515436882291D75D005E1CDF /* AddLocalAccountViewController.swift in Sources */,
 				51C452AF2265108300C03939 /* ArticleArray.swift in Sources */,
+				557EE1AE22B6F4E1004206FA /* SettingsGoogleReaderCompatibleAccountView.swift in Sources */,
 				51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */,
 				51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */,
 				51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */,
diff --git a/iOS/Settings/SettingsAddAccountView.swift b/iOS/Settings/SettingsAddAccountView.swift
index fde5175f0..212c62f04 100644
--- a/iOS/Settings/SettingsAddAccountView.swift
+++ b/iOS/Settings/SettingsAddAccountView.swift
@@ -16,6 +16,9 @@ struct SettingsAddAccountView : View {
 							   destination: SettingsLocalAccountView(name: "")).padding(.all, 4)
 			PresentationButton(SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin"),
 							   destination: SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel())).padding(.all, 4)
+			PresentationButton(SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: "Google Reader Compatible"),
+							   destination: SettingsGoogleReaderCompatibleAccountView(viewModel: SettingsGoogleReaderCompatibleAccountView.ViewModel())).padding(.all, 4)
+
 		}
 		.listStyle(.grouped)
 		.navigationBarTitle(Text("Add Account"), displayMode: .inline)
diff --git a/iOS/Settings/SettingsFeedbinAccountView.swift b/iOS/Settings/SettingsFeedbinAccountView.swift
index c867c94d2..59d7a7094 100644
--- a/iOS/Settings/SettingsFeedbinAccountView.swift
+++ b/iOS/Settings/SettingsFeedbinAccountView.swift
@@ -80,7 +80,7 @@ struct SettingsFeedbinAccountView : View {
 			switch result {
 			case .success(let authenticated):
 				
-				if authenticated {
+				if (authenticated != nil) {
 					
 					var newAccount = false
 					let workAccount: Account
diff --git a/iOS/Settings/SettingsGoogleReaderCompatibleAccountView.swift b/iOS/Settings/SettingsGoogleReaderCompatibleAccountView.swift
new file mode 100644
index 000000000..c36d71023
--- /dev/null
+++ b/iOS/Settings/SettingsGoogleReaderCompatibleAccountView.swift
@@ -0,0 +1,187 @@
+//
+//  SettingsGoogleReaderCompatibleAccountView.swift
+//  NetNewsWire-iOS
+//
+//  Created by Maurice Parker on 6/11/19.
+//  Copyright © 2019 Ranchero Software. All rights reserved.
+//
+
+import SwiftUI
+import Combine
+import Account
+import RSWeb
+
+struct SettingsGoogleReaderCompatibleAccountView : View {
+	@Environment(\.isPresented) private var isPresented
+	@ObjectBinding var viewModel: ViewModel
+	@State var busy: Bool = false
+	@State var error: Text = Text("")
+
+	var body: some View {
+		NavigationView {
+			List {
+				Section(header:
+					SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: "Google Reader Compatible").padding()
+				)  {
+					HStack {
+						Text("Email:")
+						Divider()
+						TextField($viewModel.email)
+						.textContentType(.username)
+					}
+					HStack {
+						Text("Password:")
+						Divider()
+						SecureField($viewModel.password)
+					}
+					HStack {
+						Text("API URL:")
+						Divider()
+						TextField($viewModel.apiURL)
+							.textContentType(.URL)
+					}
+				}
+				Section(footer:
+					HStack {
+						Spacer()
+						error.color(.red)
+						Spacer()
+					}
+					) {
+					HStack {
+						Spacer()
+						Button(action: { self.addAccount() }) {
+							if viewModel.isUpdate {
+								Text("Update Account")
+							} else {
+								Text("Add Account")
+							}
+						}
+						.disabled(!viewModel.isValid)
+						Spacer()
+					}
+				}
+			}
+			.disabled(busy)
+			.listStyle(.grouped)
+			.navigationBarTitle(Text(""), displayMode: .inline)
+			.navigationBarItems(leading:
+				Button(action: { self.dismiss() }) { Text("Cancel") }
+			)
+		}
+	}
+	
+	private func addAccount() {
+		
+		busy = true
+		error = Text("")
+		
+		let emailAddress = viewModel.email.trimmingCharacters(in: .whitespaces)
+		let credentials = Credentials.basic(username: emailAddress, password: viewModel.password)
+		guard let apiURL = URL(string: viewModel.apiURL) else {
+			self.error = Text("Invalide API URL.")
+			return
+		}
+
+		Account.validateCredentials(type: .googleReaderCompatible, credentials: credentials, endpoint: apiURL) { result in
+			
+			self.busy = false
+			
+			switch result {
+			case .success(let authenticated):
+				
+				if (authenticated != nil) {
+					
+					var newAccount = false
+					let workAccount: Account
+					if self.viewModel.account == nil {
+						workAccount = AccountManager.shared.createAccount(type: .googleReaderCompatible)
+						newAccount = true
+					} else {
+						workAccount = self.viewModel.account!
+					}
+					
+					do {
+						
+						do {
+							try workAccount.removeBasicCredentials()
+						} catch {}
+						
+						workAccount.endpointURL = apiURL
+						
+						try workAccount.storeCredentials(credentials)
+						
+						if newAccount {
+							workAccount.refreshAll() { result in }
+						}
+						
+						self.dismiss()
+						
+					} catch {
+						self.error = Text("Keychain error while storing credentials.")
+					}
+					
+				} else {
+					self.error = Text("Invalid email/password combination.")
+				}
+				
+			case .failure:
+				self.error = Text("Network error. Try again later.")
+			}
+			
+		}
+		
+	}
+	
+	private func dismiss() {
+		isPresented?.value = false
+	}
+	
+	class ViewModel: BindableObject {
+		let didChange = PassthroughSubject<ViewModel, Never>()
+		var account: Account? = nil
+		
+		init() {
+		}
+		
+		init(account: Account) {
+			self.account = account
+			if case .basic(let username, let password) = try? account.retrieveBasicCredentials() {
+				self.email = username
+				self.password = password
+			}
+		}
+
+		var email: String = "" {
+			didSet {
+				didChange.send(self)
+			}
+		}
+		var password: String = "" {
+			didSet {
+				didChange.send(self)
+			}
+		}
+		var apiURL: String = "" {
+			didSet {
+				didChange.send(self)
+			}
+		}
+		var isUpdate: Bool {
+			return account != nil
+		}
+		
+		var isValid: Bool {
+			return !email.isEmpty && !password.isEmpty
+		}
+	}
+	
+}
+
+#if DEBUG
+struct SettingsGoogleReaderCompatibleAccountView_Previews : PreviewProvider {
+    static var previews: some View {
+		SettingsGoogleReaderCompatibleAccountView(viewModel: SettingsGoogleReaderCompatibleAccountView.ViewModel())
+    }
+}
+#endif
diff --git a/iOS/Settings/UIKit/FeedbinAccountViewController.swift b/iOS/Settings/UIKit/FeedbinAccountViewController.swift
index c12d3d941..3cbf24bea 100644
--- a/iOS/Settings/UIKit/FeedbinAccountViewController.swift
+++ b/iOS/Settings/UIKit/FeedbinAccountViewController.swift
@@ -67,7 +67,7 @@ class FeedbinAccountViewController: UIViewController {
 			
 			switch result {
 			case .success(let authenticated):
-				if authenticated {
+				if (authenticated != nil) {
 					var newAccount = false
 					if self.account == nil {
 						self.account = AccountManager.shared.createAccount(type: .feedbin)