diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index bd3a5a57e..27c355f46 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */; }; 3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */; }; 3B826DCC2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; }; 3B826DCD2385C89600FC1ADB /* AccountsFeedWrangler.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */; }; @@ -1219,6 +1220,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAccountViewController.swift; sourceTree = ""; }; 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = ""; }; 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsFeedWranglerWindowController.swift; sourceTree = ""; }; 49F40DEF2335B71000552BF4 /* newsfoot.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = newsfoot.js; sourceTree = ""; }; @@ -1807,6 +1809,7 @@ 516A093F2361240900EAE89B /* Account.storyboard */, 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */, 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */, + 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */, ); path = Account; sourceTree = ""; @@ -3947,6 +3950,7 @@ 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, 516A09392360A2AE00EAE89B /* SettingsAccountTableViewCell.swift in Sources */, + 3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */, 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */, 51A1699C235E10D700EB091F /* AddAccountViewController.swift in Sources */, 51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */, diff --git a/iOS/Account/Account.storyboard b/iOS/Account/Account.storyboard index 7cedd59df..9c4011f8f 100644 --- a/iOS/Account/Account.storyboard +++ b/iOS/Account/Account.storyboard @@ -2,7 +2,7 @@ - + @@ -39,6 +39,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Account/FeedWranglerAccountViewController.swift b/iOS/Account/FeedWranglerAccountViewController.swift new file mode 100644 index 000000000..a5356d09a --- /dev/null +++ b/iOS/Account/FeedWranglerAccountViewController.swift @@ -0,0 +1,173 @@ +// +// FeedWranglerAccountViewController.swift +// NetNewsWire +// +// Created by Jonathan Bennett on 2019-11-24. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit +import Account +import RSWeb + +class FeedWranglerAccountViewController: UITableViewController { + + @IBOutlet weak var activityIndicator: UIActivityIndicatorView! + @IBOutlet weak var cancelBarButtonItem: UIBarButtonItem! + @IBOutlet weak var emailTextField: UITextField! + @IBOutlet weak var passwordTextField: UITextField! + @IBOutlet weak var showHideButton: UIButton! + @IBOutlet weak var actionButton: UIButton! + + weak var account: Account? + weak var delegate: AddAccountDismissDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + + activityIndicator.isHidden = true + emailTextField.delegate = self + passwordTextField.delegate = self + + if let account = account, let credentials = try? account.retrieveCredentials(type: .feedWranglerBasic) { + actionButton.setTitle(NSLocalizedString("Update Credentials", comment: "Update Credentials"), for: .normal) + emailTextField.text = credentials.username + passwordTextField.text = credentials.secret + } else { + actionButton.setTitle(NSLocalizedString("Add Account", comment: "Update Credentials"), for: .normal) + } + + NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: UITextField.textDidChangeNotification, object: emailTextField) + NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: UITextField.textDidChangeNotification, object: passwordTextField) + + tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") + } + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return section == 0 ? ImageHeaderView.rowHeight : super.tableView(tableView, heightForHeaderInSection: section) + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if section == 0 { + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView + headerView.imageView.image = AppAssets.image(for: .feedWrangler) + return headerView + } else { + return super.tableView(tableView, viewForHeaderInSection: section) + } + } + + @IBAction func cancel(_ sender: Any) { + dismiss(animated: true, completion: nil) + delegate?.dismiss() + } + + @IBAction func showHidePassword(_ sender: Any) { + if passwordTextField.isSecureTextEntry { + passwordTextField.isSecureTextEntry = false + showHideButton.setTitle(NSLocalizedString("Hide", comment: "Button Label"), for: .normal) + } else { + passwordTextField.isSecureTextEntry = true + showHideButton.setTitle(NSLocalizedString("Show", comment: "Button Label"), for: .normal) + } + } + + @IBAction func action(_ sender: Any) { + + guard let email = emailTextField.text, let password = passwordTextField.text else { + showError(NSLocalizedString("Username & password required.", comment: "Credentials Error")) + return + } + + startAnimatingActivityIndicator() + disableNavigation() + + // When you fill in the email address via auto-complete it adds extra whitespace + let trimmedEmail = email.trimmingCharacters(in: .whitespaces) + let credentials = Credentials(type: .feedWranglerBasic, username: trimmedEmail, secret: password) + Account.validateCredentials(type: .feedWrangler, credentials: credentials) { result in + + self.stopAnimtatingActivityIndicator() + self.enableNavigation() + + switch result { + case .success(let validatedCredentials): + guard let validatedCredentials = validatedCredentials else { + self.showError(NSLocalizedString("Invalid email/password combination.", comment: "Credentials Error")) + return + } + + var newAccount = false + if self.account == nil { + self.account = AccountManager.shared.createAccount(type: .feedWrangler) + newAccount = true + } + + do { + try self.account?.removeCredentials(type: .feedWranglerBasic) + try self.account?.removeCredentials(type: .feedWranglerToken) + try self.account?.storeCredentials(credentials) + try self.account?.storeCredentials(validatedCredentials) + + if newAccount { + self.account?.refreshAll { result in + switch result { + case .success: + break + case .failure(let error): + self.presentError(error) + } + } + } + + self.dismiss(animated: true, completion: nil) + self.delegate?.dismiss() + + } catch { + self.showError(NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")) + } + case .failure: + self.showError(NSLocalizedString("Network error. Try again later.", comment: "Credentials Error")) + } + + } + } + + @objc func textDidChange(_ note: Notification) { + actionButton.isEnabled = !(emailTextField.text?.isEmpty ?? false) && !(passwordTextField.text?.isEmpty ?? false) + } + + private func showError(_ message: String) { + presentError(title: "Error", message: message) + } + + private func enableNavigation() { + self.cancelBarButtonItem.isEnabled = true + self.actionButton.isEnabled = true + } + + private func disableNavigation() { + cancelBarButtonItem.isEnabled = false + actionButton.isEnabled = false + } + + private func startAnimatingActivityIndicator() { + activityIndicator.isHidden = false + activityIndicator.startAnimating() + } + + private func stopAnimtatingActivityIndicator() { + self.activityIndicator.isHidden = true + self.activityIndicator.stopAnimating() + } + +} + +extension FeedWranglerAccountViewController: UITextFieldDelegate { + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + +} diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 081725a9a..ed2048aa1 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -26,6 +26,10 @@ struct AppAssets { static var accountFeedlyImage: UIImage = { return UIImage(named: "accountFeedly")! }() + + static var accountFeedWranglerImage: UIImage = { + return UIImage(named: "accountFeedWrangler")! + }() static var accountFreshRSSImage: UIImage = { return UIImage(named: "accountFreshRSS")! @@ -214,6 +218,8 @@ struct AppAssets { return AppAssets.accountFeedbinImage case .feedly: return AppAssets.accountFeedlyImage + case .feedWrangler: + return AppAssets.accountFeedWranglerImage case .freshRSS: return AppAssets.accountFreshRSSImage default: diff --git a/iOS/Resources/Assets.xcassets/accountFeedWrangler.imageset/Contents.json b/iOS/Resources/Assets.xcassets/accountFeedWrangler.imageset/Contents.json new file mode 100644 index 000000000..0721d5d1f --- /dev/null +++ b/iOS/Resources/Assets.xcassets/accountFeedWrangler.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "outline-512.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/iOS/Resources/Assets.xcassets/accountFeedWrangler.imageset/outline-512.png b/iOS/Resources/Assets.xcassets/accountFeedWrangler.imageset/outline-512.png new file mode 100644 index 000000000..f7d29faa1 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/accountFeedWrangler.imageset/outline-512.png differ diff --git a/iOS/Settings/AddAccountViewController.swift b/iOS/Settings/AddAccountViewController.swift index 0f803b8a6..30d7d0ce1 100644 --- a/iOS/Settings/AddAccountViewController.swift +++ b/iOS/Settings/AddAccountViewController.swift @@ -43,6 +43,12 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate addAccount.delegate = self addAccount.presentationAnchor = self.view.window! OperationQueue.main.addOperation(addAccount) + case 3: + let navController = UIStoryboard.account.instantiateViewController(withIdentifier: "FeedWranglerAccountNavigationViewController") as! UINavigationController + navController.modalPresentationStyle = .currentContext + let addViewController = navController.topViewController as! FeedWranglerAccountViewController + addViewController.delegate = self + present(navController, animated: true) default: break } diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index c81c9ed12..fb985e5b9 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -2,7 +2,7 @@ - + @@ -19,7 +19,7 @@ - + @@ -267,7 +267,7 @@ - + @@ -284,7 +284,7 @@ - + @@ -301,7 +301,7 @@ - + @@ -318,14 +318,14 @@ - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -842,6 +875,7 @@ +