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 !
2021-06-16 08:41:51 +02:00
@IBOutlet weak var onepasswordButton : UIBarButtonItem ! {
didSet {
onepasswordButton . image ? . withTintColor ( AppAssets . primaryAccentColor )
}
}
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 " )
2021-04-25 10:46:24 +02:00
apiURLTextField . placeholder = NSLocalizedString ( " API URL: fresh.rss.net/api/greader.php " , comment : " FreshRSS API Helper " )
2020-10-26 02:03:32 +01:00
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 :
2021-06-01 01:01:01 +02:00
footerLabel . text = NSLocalizedString ( " Sign in to your BazQux account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain. \n \n Don’ t have a BazQux account? " , comment : " BazQux " )
2020-11-09 14:31:29 +01:00
signUpButton . setTitle ( NSLocalizedString ( " Sign Up Here " , comment : " BazQux SignUp " ) , for : . normal )
case . inoreader :
2021-06-01 01:01:01 +02:00
footerLabel . text = NSLocalizedString ( " Sign in to your InoReader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain. \n \n Don’ t have an InoReader account? " , comment : " InoReader " )
2020-11-09 14:31:29 +01:00
signUpButton . setTitle ( NSLocalizedString ( " Sign Up Here " , comment : " InoReader SignUp " ) , for : . normal )
case . theOldReader :
2021-06-01 01:01:01 +02:00
footerLabel . text = NSLocalizedString ( " Sign in to your The Old Reader account and sync your feeds 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 " )
2020-11-09 14:31:29 +01:00
signUpButton . setTitle ( NSLocalizedString ( " Sign Up Here " , comment : " TOR SignUp " ) , for : . normal )
case . freshRSS :
2021-06-01 01:01:01 +02:00
footerLabel . text = NSLocalizedString ( " Sign in to your FreshRSS instance and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain. \n \n Don’ t have an FreshRSS instance? " , comment : " FreshRSS " )
2020-11-09 14:31:29 +01:00
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 )
2024-03-11 06:22:41 +01:00
Account . validateCredentials ( type : type , credentials : credentials , endpoint : url , secretsProvider : Secrets ( ) ) { result in
2020-10-26 02:03:32 +01:00
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 )
2024-03-27 01:31:46 +01:00
Task { @ MainActor in
do {
try await self . account ? . refreshAll ( )
} catch {
2021-01-09 22:15:14 +01:00
self . showError ( NSLocalizedString ( error . localizedDescription , comment : " Accoount Refresh Error " ) )
2020-10-26 02:03:32 +01:00
}
}
2024-03-27 01:31:46 +01:00
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 )
}
2021-06-16 08:41:51 +02:00
@IBAction func retrievePasswordDetailsFrom1Password ( _ sender : Any ) {
var url : String
switch accountType {
case . bazQux :
url = " bazqux.com "
case . inoreader :
url = " inoreader.com "
case . theOldReader :
url = " theoldreader.com "
case . freshRSS :
url = apiURLTextField . text ? ? " "
2021-06-16 08:55:11 +02:00
default :
url = " "
2021-06-16 08:41:51 +02:00
}
2021-06-17 06:38:53 +02:00
OnePasswordExtension . shared ( ) . findLogin ( forURLString : url , for : self , sender : sender ) { [ self ] loginDictionary , error in
2021-06-16 08:41:51 +02:00
if let loginDictionary = loginDictionary {
usernameTextField . text = loginDictionary [ AppExtensionUsernameKey ] as ? String
passwordTextField . text = loginDictionary [ AppExtensionPasswordKey ] as ? String
actionButton . isEnabled = ! ( usernameTextField . text ? . isEmpty ? ? false ) && ! ( passwordTextField . text ? . isEmpty ? ? false )
}
}
}
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
}
}