2020-10-26 02:03:32 +01:00
//
// R e a d e r A P I A c c o u n t V i e w C o n t r o l l e r . s w i f t
// N e t N e w s W i r e - i O S
//
// C r e a t e d b y S t u a r t B r e c k e n r i d g e o n 2 5 / 1 0 / 2 0 .
// C o p y r i g h t © 2 0 2 0 R a n c h e r o S o f t w a r e . A l l r i g h t s r e s e r v e d .
//
import UIKit
import Account
import Secrets
import RSWeb
2020-11-09 14:31:29 +01:00
import SafariServices
2020-10-26 02:03:32 +01:00
class ReaderAPIAccountViewController : UITableViewController {
@IBOutlet weak var activityIndicator : UIActivityIndicatorView !
@IBOutlet weak var cancelBarButtonItem : UIBarButtonItem !
@IBOutlet weak var usernameTextField : UITextField !
@IBOutlet weak var passwordTextField : UITextField !
@IBOutlet weak var apiURLTextField : UITextField !
@IBOutlet weak var showHideButton : UIButton !
@IBOutlet weak var actionButton : UIButton !
2020-11-09 14:31:29 +01:00
@IBOutlet weak var footerLabel : UILabel !
@IBOutlet weak var signUpButton : UIButton !
2020-10-26 02:03:32 +01:00
weak var account : Account ?
var accountType : AccountType ?
weak var delegate : AddAccountDismissDelegate ?
override func viewDidLoad ( ) {
super . viewDidLoad ( )
2020-11-09 14:41:05 +01:00
setupFooter ( )
2020-10-26 02:03:32 +01:00
activityIndicator . isHidden = true
usernameTextField . delegate = self
passwordTextField . delegate = self
if let unwrappedAcount = account ,
let credentials = try ? retrieveCredentialsForAccount ( for : unwrappedAcount ) {
actionButton . setTitle ( NSLocalizedString ( " Update Credentials " , comment : " Update Credentials " ) , for : . normal )
actionButton . isEnabled = true
usernameTextField . text = credentials . username
passwordTextField . text = credentials . secret
} else {
actionButton . setTitle ( NSLocalizedString ( " Add Account " , comment : " Add Account " ) , for : . normal )
}
if let unwrappedAccountType = accountType {
switch unwrappedAccountType {
case . freshRSS :
title = NSLocalizedString ( " FreshRSS " , comment : " FreshRSS " )
case . inoreader :
title = NSLocalizedString ( " InoReader " , comment : " InoReader " )
case . bazQux :
title = NSLocalizedString ( " BazQux " , comment : " BazQux " )
case . theOldReader :
title = NSLocalizedString ( " The Old Reader " , comment : " The Old Reader " )
default :
title = " "
}
}
NotificationCenter . default . addObserver ( self , selector : #selector ( textDidChange ( _ : ) ) , name : UITextField . textDidChangeNotification , object : usernameTextField )
NotificationCenter . default . addObserver ( self , selector : #selector ( textDidChange ( _ : ) ) , name : UITextField . textDidChangeNotification , object : passwordTextField )
tableView . register ( ImageHeaderView . self , forHeaderFooterViewReuseIdentifier : " SectionHeader " )
}
2020-11-09 14:31:29 +01:00
private func setupFooter ( ) {
switch accountType {
case . bazQux :
footerLabel . text = NSLocalizedString ( " Sign in to your BazQux account and sync your subscriptions across your devices. Your username and password will be encrypted and stored in Keychain. \n \n Don't have a BazQux account? " , comment : " BazQux " )
signUpButton . setTitle ( NSLocalizedString ( " Sign Up Here " , comment : " BazQux SignUp " ) , for : . normal )
case . inoreader :
footerLabel . text = NSLocalizedString ( " Sign in to your InoReader account and sync your subscriptions across your devices. Your username and password will be encrypted and stored in Keychain. \n \n Don't have an InoReader account? " , comment : " InoReader " )
signUpButton . setTitle ( NSLocalizedString ( " Sign Up Here " , comment : " InoReader SignUp " ) , for : . normal )
case . theOldReader :
footerLabel . text = NSLocalizedString ( " Sign in to your The Old Reader account and sync your subscriptions across your devices. Your username and password will be encrypted and stored in Keychain. \n \n Don't have a The Old Reader account? " , comment : " TOR " )
signUpButton . setTitle ( NSLocalizedString ( " Sign Up Here " , comment : " TOR SignUp " ) , for : . normal )
case . freshRSS :
footerLabel . text = NSLocalizedString ( " Sign in to your FreshRSS instance and sync your subscriptions across your devices. Your username and password will be encrypted and stored in Keychain. \n \n Don't have an FreshRSS instance? " , comment : " FreshRSS " )
signUpButton . setTitle ( NSLocalizedString ( " Find Out More " , comment : " FreshRSS SignUp " ) , for : . normal )
default :
return
}
}
2020-10-26 02:03:32 +01:00
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 = headerViewImage ( )
return headerView
} else {
return super . tableView ( tableView , viewForHeaderInSection : section )
}
}
override func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int {
switch section {
case 0 :
switch accountType {
case . freshRSS :
return 3
default :
return 2
}
default :
return 1
}
}
@IBAction func cancel ( _ sender : Any ) {
dismiss ( animated : true , completion : nil )
}
@IBAction func showHidePassword ( _ sender : Any ) {
if passwordTextField . isSecureTextEntry {
passwordTextField . isSecureTextEntry = false
showHideButton . setTitle ( " Hide " , for : . normal )
} else {
passwordTextField . isSecureTextEntry = true
showHideButton . setTitle ( " Show " , for : . normal )
}
}
@IBAction func action ( _ sender : Any ) {
2020-10-29 20:05:55 +01:00
guard validateDataEntry ( ) , let type = accountType else {
2020-10-27 20:45:21 +01:00
return
}
2020-10-26 02:03:32 +01:00
let username = usernameTextField . text !
let password = passwordTextField . text !
let url = apiURL ( ) !
// W h e n y o u f i l l i n t h e e m a i l a d d r e s s v i a a u t o - c o m p l e t e i t a d d s e x t r a w h i t e s p a c e
let trimmedUsername = username . trimmingCharacters ( in : . whitespaces )
2020-10-29 20:05:55 +01:00
guard account != nil || ! AccountManager . shared . duplicateServiceAccount ( type : type , username : trimmedUsername ) else {
showError ( NSLocalizedString ( " There is already an account of that type with that username created. " , comment : " Duplicate Error " ) )
2020-10-26 02:03:32 +01:00
return
}
2020-10-29 20:05:55 +01:00
startAnimatingActivityIndicator ( )
disableNavigation ( )
let credentials = Credentials ( type : . readerBasic , username : trimmedUsername , secret : password )
2020-10-26 02:03:32 +01:00
Account . validateCredentials ( type : type , credentials : credentials , endpoint : url ) { result in
self . stopAnimatingActivityIndicator ( )
self . enableNavigation ( )
switch result {
case . success ( let validatedCredentials ) :
if let validatedCredentials = validatedCredentials {
2021-01-09 22:15:14 +01:00
2020-10-26 02:03:32 +01:00
if self . account = = nil {
self . account = AccountManager . shared . createAccount ( type : type )
}
do {
self . account ? . endpointURL = url
2021-01-09 22:15:14 +01:00
try ? self . account ? . removeCredentials ( type : . readerBasic )
try ? self . account ? . removeCredentials ( type : . readerAPIKey )
2020-10-26 02:03:32 +01:00
try self . account ? . storeCredentials ( credentials )
try self . account ? . storeCredentials ( validatedCredentials )
self . dismiss ( animated : true , completion : nil )
2021-01-09 22:15:14 +01:00
self . account ? . refreshAll ( ) { result in
switch result {
case . success :
break
case . failure ( let error ) :
self . showError ( NSLocalizedString ( error . localizedDescription , comment : " Accoount Refresh Error " ) )
2020-10-26 02:03:32 +01:00
}
}
self . delegate ? . dismiss ( )
} catch {
self . showError ( NSLocalizedString ( " Keychain error while storing credentials. " , comment : " Credentials Error " ) )
}
} else {
self . showError ( NSLocalizedString ( " Invalid username/password combination. " , comment : " Credentials Error " ) )
}
case . failure ( let error ) :
self . showError ( error . localizedDescription )
}
}
}
private func retrieveCredentialsForAccount ( for account : Account ) throws -> Credentials ? {
switch accountType {
case . bazQux , . inoreader , . theOldReader , . freshRSS :
return try account . retrieveCredentials ( type : . readerBasic )
default :
return nil
}
}
private func headerViewImage ( ) -> UIImage ? {
if let accountType = accountType {
switch accountType {
case . bazQux :
return AppAssets . accountBazQuxImage
case . inoreader :
return AppAssets . accountInoreaderImage
case . theOldReader :
return AppAssets . accountTheOldReaderImage
case . freshRSS :
return AppAssets . accountFreshRSSImage
default :
return nil
}
}
return nil
}
2020-10-27 20:45:21 +01:00
private func validateDataEntry ( ) -> Bool {
2020-10-26 02:03:32 +01:00
switch accountType {
case . freshRSS :
if ! usernameTextField . hasText || ! passwordTextField . hasText || ! apiURLTextField . hasText {
showError ( NSLocalizedString ( " Username, password, and API URL are required. " , comment : " Credentials Error " ) )
2020-10-27 20:45:21 +01:00
return false
2020-10-26 02:03:32 +01:00
}
guard let _ = URL ( string : apiURLTextField . text ! ) else {
showError ( NSLocalizedString ( " Invalid API URL. " , comment : " Invalid API URL " ) )
2020-10-27 20:45:21 +01:00
return false
2020-10-26 02:03:32 +01:00
}
default :
if ! usernameTextField . hasText || ! passwordTextField . hasText {
showError ( NSLocalizedString ( " Username and password are required. " , comment : " Credentials Error " ) )
2020-10-27 20:45:21 +01:00
return false
2020-10-26 02:03:32 +01:00
}
}
2020-10-27 20:45:21 +01:00
return true
2020-10-26 02:03:32 +01:00
}
2020-11-09 14:31:29 +01:00
@IBAction func signUpWithProvider ( _ sender : Any ) {
var url : URL !
switch accountType {
case . bazQux :
url = URL ( string : " https://bazqux.com " ) !
case . inoreader :
url = URL ( string : " https://www.inoreader.com " ) !
case . theOldReader :
url = URL ( string : " https://theoldreader.com " ) !
case . freshRSS :
url = URL ( string : " https://freshrss.org " ) !
default :
return
}
let safari = SFSafariViewController ( url : url )
safari . modalPresentationStyle = . currentContext
self . present ( safari , animated : true , completion : nil )
}
2020-10-26 02:03:32 +01:00
private func apiURL ( ) -> URL ? {
switch accountType {
case . freshRSS :
return URL ( string : apiURLTextField . text ! ) !
case . inoreader :
return URL ( string : ReaderAPIVariant . inoreader . host ) !
case . bazQux :
return URL ( string : ReaderAPIVariant . bazQux . host ) !
case . theOldReader :
return URL ( string : ReaderAPIVariant . theOldReader . host ) !
default :
return nil
}
}
2020-11-09 14:31:29 +01:00
2020-10-26 02:03:32 +01:00
@objc func textDidChange ( _ note : Notification ) {
actionButton . isEnabled = ! ( usernameTextField . 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 stopAnimatingActivityIndicator ( ) {
self . activityIndicator . isHidden = true
self . activityIndicator . stopAnimating ( )
}
}
extension ReaderAPIAccountViewController : UITextFieldDelegate {
func textFieldShouldReturn ( _ textField : UITextField ) -> Bool {
textField . resignFirstResponder ( )
return true
}
}