diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index b6ef62f7c..da07e1f86 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -107,6 +107,13 @@ 51C452B82265178500C03939 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 51C452B72265178500C03939 /* styleSheet.css */; }; 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */; }; + 51F85BE5227217D000C787DC /* RefreshIntervalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BDB2272162F00C787DC /* RefreshIntervalViewController.swift */; }; + 51F85BE7227245FC00C787DC /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BE6227245FC00C787DC /* AboutViewController.swift */; }; + 51F85BEB22724CB600C787DC /* About.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BEA22724CB600C787DC /* About.rtf */; }; + 51F85BED227251DF00C787DC /* Acknowledgments.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BEC227251DF00C787DC /* Acknowledgments.rtf */; }; + 51F85BEF2272520B00C787DC /* Thanks.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BEE2272520B00C787DC /* Thanks.rtf */; }; + 51F85BF12272524100C787DC /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BF02272524100C787DC /* Credits.rtf */; }; + 51F85BF32272531500C787DC /* Dedication.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BF22272531500C787DC /* Dedication.rtf */; }; 6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; }; 6581C73A20CED60100F4AD34 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */; }; 6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; }; @@ -652,6 +659,13 @@ 51C452B32265141B00C03939 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; 51C452B72265178500C03939 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FolderTreeMenu.swift; path = AddFeed/FolderTreeMenu.swift; sourceTree = ""; }; + 51F85BDB2272162F00C787DC /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.swift; sourceTree = ""; }; + 51F85BE6227245FC00C787DC /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; + 51F85BEA22724CB600C787DC /* About.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = About.rtf; sourceTree = ""; }; + 51F85BEC227251DF00C787DC /* Acknowledgments.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Acknowledgments.rtf; sourceTree = ""; }; + 51F85BEE2272520B00C787DC /* Thanks.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Thanks.rtf; sourceTree = ""; }; + 51F85BF02272524100C787DC /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; + 51F85BF22272531500C787DC /* Dedication.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Dedication.rtf; sourceTree = ""; }; 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 6581C73420CED60100F4AD34 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = ""; }; @@ -943,6 +957,8 @@ children = ( 5183CCEC22711DCE0010922C /* Settings.storyboard */, 5183CCEE227125970010922C /* SettingsViewController.swift */, + 51F85BDB2272162F00C787DC /* RefreshIntervalViewController.swift */, + 51F85BE6227245FC00C787DC /* AboutViewController.swift */, ); path = Settings; sourceTree = ""; @@ -1578,6 +1594,11 @@ 84C9FC9A2262A1A900D921D6 /* Resources */ = { isa = PBXGroup; children = ( + 51F85BEA22724CB600C787DC /* About.rtf */, + 51F85BF02272524100C787DC /* Credits.rtf */, + 51F85BEC227251DF00C787DC /* Acknowledgments.rtf */, + 51F85BEE2272520B00C787DC /* Thanks.rtf */, + 51F85BF22272531500C787DC /* Dedication.rtf */, 51C452B72265178500C03939 /* styleSheet.css */, 84C9FC9B2262A1A900D921D6 /* Assets.xcassets */, 84C9FC9C2262A1A900D921D6 /* Info.plist */, @@ -2084,10 +2105,15 @@ files = ( 51C452862265093600C03939 /* Add.storyboard in Resources */, 84C9FCA12262A1B300D921D6 /* Main.storyboard in Resources */, + 51F85BF32272531500C787DC /* Dedication.rtf in Resources */, 84C9FCA42262A1B800D921D6 /* LaunchScreen.storyboard in Resources */, + 51F85BEB22724CB600C787DC /* About.rtf in Resources */, 5183CCED22711DCE0010922C /* Settings.storyboard in Resources */, + 51F85BED227251DF00C787DC /* Acknowledgments.rtf in Resources */, 51C452AB22650DC600C03939 /* template.html in Resources */, + 51F85BF12272524100C787DC /* Credits.rtf in Resources */, 84A3EE61223B667F00557320 /* DefaultFeeds.opml in Resources */, + 51F85BEF2272520B00C787DC /* Thanks.rtf in Resources */, 84C9FC9D2262A1A900D921D6 /* Assets.xcassets in Resources */, 51C452B82265178500C03939 /* styleSheet.css in Resources */, ); @@ -2210,11 +2236,13 @@ 5126EE97226CB48A00C22AFC /* NavigationStateController.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, 5183CCEF227125970010922C /* SettingsViewController.swift in Sources */, + 51F85BE5227217D000C787DC /* RefreshIntervalViewController.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, 5183CCDF226F1FCC0010922C /* UINavigationController+Progress.swift in Sources */, 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */, 512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */, 51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */, + 51F85BE7227245FC00C787DC /* AboutViewController.swift in Sources */, 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */, 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */, 84F3EE1B20DEC97E003FADEB /* HTMLFeedFinder.swift in Sources */, diff --git a/Shared/Refresh/RefreshInterval.swift b/Shared/Refresh/RefreshInterval.swift index ffb05a9cd..c32746cdb 100644 --- a/Shared/Refresh/RefreshInterval.swift +++ b/Shared/Refresh/RefreshInterval.swift @@ -35,4 +35,24 @@ enum RefreshInterval: Int { return 8 * 60 * 60 } } + + func description() -> String { + switch self { + case .manually: + return NSLocalizedString("Manually", comment: "Manually") + case .every10Minutes: + return NSLocalizedString("Every 10 Minutes", comment: "Every 10 Minutes") + case .every30Minutes: + return NSLocalizedString("Every 30 Minutes", comment: "Every 30 Minutes") + case .everyHour: + return NSLocalizedString("Every Hour", comment: "Every Hour") + case .every2Hours: + return NSLocalizedString("Every 2 Hours", comment: "Every 2 Hours") + case .every4Hours: + return NSLocalizedString("Every 4 Hours", comment: "Every 4 Hours") + case .every8Hours: + return NSLocalizedString("Every 8 Hours", comment: "Every 8 Hours") + } + } + } diff --git a/iOS/Add/AddContainerViewController.swift b/iOS/Add/AddContainerViewController.swift index 6e656c350..28ed46514 100644 --- a/iOS/Add/AddContainerViewController.swift +++ b/iOS/Add/AddContainerViewController.swift @@ -31,6 +31,9 @@ class AddContainerViewController: UIViewController { private var currentViewController: AddContainerViewControllerChild? + var initialFeed: String? + var initialFeedName: String? + override func viewDidLoad() { super.viewDidLoad() @@ -91,21 +94,32 @@ extension AddContainerViewController: AddContainerViewControllerChildDelegate { private extension AddContainerViewController { func switchToFeed() { + guard !(currentViewController is AddFeedViewController) else { return } + resetUI() hideCurrentController() - displayContentController(UIStoryboard.add.instantiateController(ofType: AddFeedViewController.self)) + + let addFeedController = UIStoryboard.add.instantiateController(ofType: AddFeedViewController.self) + addFeedController.initialFeed = initialFeed + addFeedController.initialFeedName = initialFeedName + + displayContentController(addFeedController) + } func switchToFolder() { + guard !(currentViewController is AddFolderViewController) else { return } + resetUI() hideCurrentController() displayContentController(UIStoryboard.add.instantiateController(ofType: AddFolderViewController.self)) + } func resetUI() { diff --git a/iOS/Add/AddFeedViewController.swift b/iOS/Add/AddFeedViewController.swift index d5984b85c..f92116a41 100644 --- a/iOS/Add/AddFeedViewController.swift +++ b/iOS/Add/AddFeedViewController.swift @@ -32,14 +32,23 @@ class AddFeedViewController: UITableViewController, AddContainerViewControllerCh private var userCancelled = false - var delegate: AddContainerViewControllerChildDelegate? - + weak var delegate: AddContainerViewControllerChildDelegate? + var initialFeed: String? + var initialFeedName: String? + override func viewDidLoad() { super.viewDidLoad() urlTextField.autocorrectionType = .no urlTextField.autocapitalizationType = .none + urlTextField.text = initialFeed + + if initialFeed != nil { + delegate?.readyToAdd(state: true) + } + + nameTextField.text = initialFeedName pickerData = AddFeedFolderPickerData() folderPickerView.dataSource = self diff --git a/iOS/Add/AddFolderViewController.swift b/iOS/Add/AddFolderViewController.swift index c91abda0f..1ad7be468 100644 --- a/iOS/Add/AddFolderViewController.swift +++ b/iOS/Add/AddFolderViewController.swift @@ -18,7 +18,7 @@ class AddFolderViewController: UITableViewController, AddContainerViewController private var accounts: [Account]! - var delegate: AddContainerViewControllerChildDelegate? + weak var delegate: AddContainerViewControllerChildDelegate? override func viewDidLoad() { diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index c29ac5c88..bb176ca8c 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -353,55 +353,13 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn @IBAction func settings(_ sender: UIBarButtonItem) { - let settingsViewController = UIStoryboard.settings.instantiateInitialViewController()! - settingsViewController.modalPresentationStyle = .formSheet - self.present(settingsViewController, animated: true) + let settingsNavViewController = UIStoryboard.settings.instantiateInitialViewController() as! UINavigationController + settingsNavViewController.modalPresentationStyle = .formSheet -// let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) -// -// // Settings Button -// let settingsTitle = NSLocalizedString("Settings", comment: "Settings") -// let setting = UIAlertAction(title: settingsTitle, style: .default) { alertAction in -// -// } -// optionMenu.addAction(setting) -// -// // Import Button -// let importOPMLTitle = NSLocalizedString("Import OPML", comment: "Import OPML") -// let importOPML = UIAlertAction(title: importOPMLTitle, style: .default) { [unowned self] alertAction in -// let docPicker = UIDocumentPickerViewController(documentTypes: ["public.xml", "org.opml.opml"], in: .import) -// docPicker.delegate = self -// docPicker.modalPresentationStyle = .formSheet -// self.present(docPicker, animated: true) -// } -// optionMenu.addAction(importOPML) -// -// // Export Button -// let exportOPMLTitle = NSLocalizedString("Export OPML", comment: "Export OPML") -// let exportOPML = UIAlertAction(title: exportOPMLTitle, style: .default) { [unowned self] alertAction in -// -// let filename = "MySubscriptions.opml" -// let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(filename) -// let opmlString = OPMLExporter.OPMLString(with: AccountManager.shared.localAccount, title: filename) -// do { -// try opmlString.write(to: tempFile, atomically: true, encoding: String.Encoding.utf8) -// } catch { -// self.presentError(title: "OPML Export Error", message: error.localizedDescription) -// } -// -// let docPicker = UIDocumentPickerViewController(url: tempFile, in: .exportToService) -// docPicker.modalPresentationStyle = .formSheet -// self.present(docPicker, animated: true) -// -// } -// optionMenu.addAction(exportOPML) -// optionMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel)) -// -// if let popoverController = optionMenu.popoverPresentationController { -// popoverController.barButtonItem = sender -// } -// -// self.present(optionMenu, animated: true) + let settingsViewController = settingsNavViewController.topViewController as! SettingsViewController + settingsViewController.presentingParentController = self + + self.present(settingsNavViewController, animated: true) } @@ -583,24 +541,6 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn } -// MARK: OPML Document Picker - -extension MasterFeedViewController: UIDocumentPickerDelegate { - - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - - for url in urls { - do { - try OPMLImporter.parseAndImport(fileURL: url, account: AccountManager.shared.localAccount) - } catch { - presentError(title: "OPML Import Error", message: error.localizedDescription) - } - } - - } - -} - // MARK: MasterTableViewCellDelegate extension MasterFeedViewController: MasterFeedTableViewCellDelegate { diff --git a/iOS/Resources/About.rtf b/iOS/Resources/About.rtf new file mode 100644 index 000000000..d1351c2f6 --- /dev/null +++ b/iOS/Resources/About.rtf @@ -0,0 +1,11 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf400 +{\fonttbl\f0\fnil\fcharset0 LucidaGrande-Bold;} +{\colortbl;\red255\green255\blue255;\red0\green0\blue0;} +{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;} +\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\sl360\slmult1\pardirnatural\partightenfactor0 + +\f0\b\fs28 \cf2 By Brent Simmons +\fs22 \ +{\field{\*\fldinst{HYPERLINK "http://inessential.com/"}}{\fldrslt +\fs28 inessential.com}}} \ No newline at end of file diff --git a/iOS/Resources/Acknowledgments.rtf b/iOS/Resources/Acknowledgments.rtf new file mode 100644 index 000000000..fdfda021a --- /dev/null +++ b/iOS/Resources/Acknowledgments.rtf @@ -0,0 +1,10 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf400 +{\fonttbl\f0\fnil\fcharset0 LucidaGrande;} +{\colortbl;\red255\green255\blue255;\red0\green0\blue0;} +{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;} +\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\deftab720 +\pard\pardeftab720\li360\sa60\partightenfactor0 +{\field{\*\fldinst{HYPERLINK "https://github.com/ccgus/fmdb"}}{\fldrslt +\f0\fs28 \cf2 FMDB}} +\f0\fs28 \cf2 (greatest SQLite wrapper ever in history) is by {\field{\*\fldinst{HYPERLINK "http://flyingmeat.com/"}}{\fldrslt \cf2 Flying Meat Software}}.} \ No newline at end of file diff --git a/iOS/Resources/Credits.rtf b/iOS/Resources/Credits.rtf new file mode 100644 index 000000000..485b07abb --- /dev/null +++ b/iOS/Resources/Credits.rtf @@ -0,0 +1,10 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf400 +{\fonttbl\f0\fnil\fcharset0 LucidaGrande;} +{\colortbl;\red255\green255\blue255;\red0\green0\blue0;} +{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;} +\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\deftab720 +\pard\pardeftab720\li360\sa60\partightenfactor0 + +\f0\fs28 \cf2 App icon and most other icons: {\field{\*\fldinst{HYPERLINK "https://twitter.com/BradEllis"}}{\fldrslt Brad Ellis}}\ +Major code contributors: {\field{\*\fldinst{HYPERLINK "https://github.com/olofhellman"}}{\fldrslt Olof Hellman}}, {\field{\*\fldinst{HYPERLINK "https://github.com/vincode-io"}}{\fldrslt Maurice Parker}}, and {\field{\*\fldinst{HYPERLINK "https://github.com/danielpunkass"}}{\fldrslt Daniel Jalkut}}} \ No newline at end of file diff --git a/iOS/Resources/Dedication.rtf b/iOS/Resources/Dedication.rtf new file mode 100644 index 000000000..d5861fdf3 --- /dev/null +++ b/iOS/Resources/Dedication.rtf @@ -0,0 +1,9 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf400 +{\fonttbl\f0\fnil\fcharset0 LucidaGrande;} +{\colortbl;\red255\green255\blue255;\red0\green0\blue0;} +{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;} +\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\deftab720 +\pard\pardeftab720\li360\sa60\partightenfactor0 + +\f0\fs28 \cf2 NetNewsWire 5.0 is dedicated to Aaron Swartz, Derek Miller, and Alex King, who helped with earlier versions of NetNewsWire, who I miss.} \ No newline at end of file diff --git a/iOS/Resources/Thanks.rtf b/iOS/Resources/Thanks.rtf new file mode 100644 index 000000000..1d6c6249f --- /dev/null +++ b/iOS/Resources/Thanks.rtf @@ -0,0 +1,9 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf400 +{\fonttbl\f0\fnil\fcharset0 LucidaGrande;} +{\colortbl;\red255\green255\blue255;\red0\green0\blue0;} +{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;} +\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\deftab720 +\pard\pardeftab720\li360\sa60\partightenfactor0 + +\f0\fs28 \cf2 Thanks to Sheila and my family; thanks to my friends in Seattle and around the globe; thanks to my co-workers and friends at {\field{\*\fldinst{HYPERLINK "https://www.omnigroup.com/"}}{\fldrslt \cf2 The Omni Group}}; thanks to the ever-patient and ever-awesome NetNewsWire beta testers.} \ No newline at end of file diff --git a/iOS/Settings/AboutViewController.swift b/iOS/Settings/AboutViewController.swift new file mode 100644 index 000000000..e22ea5db5 --- /dev/null +++ b/iOS/Settings/AboutViewController.swift @@ -0,0 +1,45 @@ +// +// AboutViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/25/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class AboutViewController: UITableViewController { + + @IBOutlet weak var aboutTextView: UITextView! + @IBOutlet weak var creditsTextView: UITextView! + @IBOutlet weak var acknowledgmentsTextView: UITextView! + @IBOutlet weak var thanksTextView: UITextView! + @IBOutlet weak var dedicationTextView: UITextView! + + override func viewDidLoad() { + + super.viewDidLoad() + + configureCell(file: "About", textView: aboutTextView) + configureCell(file: "Credits", textView: creditsTextView) + configureCell(file: "Acknowledgments", textView: acknowledgmentsTextView) + configureCell(file: "Thanks", textView: thanksTextView) + configureCell(file: "Dedication", textView: dedicationTextView) + + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableView.automaticDimension + } + +} + +private extension AboutViewController { + + func configureCell(file: String, textView: UITextView) { + let url = Bundle.main.url(forResource: file, withExtension: "rtf")! + let string = try! NSAttributedString(url: url, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil) + textView.attributedText = string + } + +} diff --git a/iOS/Settings/RefreshIntervalViewController.swift b/iOS/Settings/RefreshIntervalViewController.swift new file mode 100644 index 000000000..6d7f24283 --- /dev/null +++ b/iOS/Settings/RefreshIntervalViewController.swift @@ -0,0 +1,108 @@ +// +// RefreshIntervalViewController.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/25/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class RefreshIntervalViewController: UITableViewController { + + override func numberOfSections(in tableView: UITableView) -> Int { + // #warning Incomplete implementation, return the number of sections + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 7 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + let userRefreshInterval = AppDefaults.refreshInterval + + switch indexPath.row { + case 0: + cell.textLabel?.text = RefreshInterval.manually.description() + if userRefreshInterval == RefreshInterval.manually { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + case 1: + cell.textLabel?.text = RefreshInterval.every10Minutes.description() + if userRefreshInterval == RefreshInterval.every10Minutes { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + case 2: + cell.textLabel?.text = RefreshInterval.every30Minutes.description() + if userRefreshInterval == RefreshInterval.every30Minutes { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + case 3: + cell.textLabel?.text = RefreshInterval.everyHour.description() + if userRefreshInterval == RefreshInterval.everyHour { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + case 4: + cell.textLabel?.text = RefreshInterval.every2Hours.description() + if userRefreshInterval == RefreshInterval.every2Hours { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + case 5: + cell.textLabel?.text = RefreshInterval.every4Hours.description() + if userRefreshInterval == RefreshInterval.every4Hours { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + default: + cell.textLabel?.text = RefreshInterval.every8Hours.description() + if userRefreshInterval == RefreshInterval.every8Hours { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + } + return cell + + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + let refreshInterval: RefreshInterval + + switch indexPath.row { + case 0: + refreshInterval = RefreshInterval.manually + case 1: + refreshInterval = RefreshInterval.every10Minutes + case 2: + refreshInterval = RefreshInterval.every30Minutes + case 3: + refreshInterval = RefreshInterval.everyHour + case 4: + refreshInterval = RefreshInterval.every2Hours + case 5: + refreshInterval = RefreshInterval.every4Hours + default: + refreshInterval = RefreshInterval.every8Hours + } + + AppDefaults.refreshInterval = refreshInterval + self.navigationController?.popViewController(animated: true) + + } + +} diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index 4d3c41be6..aaf30f457 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -166,6 +166,9 @@ + + + @@ -274,11 +277,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift index 5ee05b785..281b7f805 100644 --- a/iOS/Settings/SettingsViewController.swift +++ b/iOS/Settings/SettingsViewController.swift @@ -7,28 +7,146 @@ // import UIKit +import Account class SettingsViewController: UITableViewController { - override func viewDidLoad() { - super.viewDidLoad() + @IBOutlet weak var refreshIntervalLabel: UILabel! + @IBOutlet weak var timelineSortOrderSwitch: UISwitch! + + weak var presentingParentController: UIViewController? + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if AppDefaults.timelineSortDirection == .orderedAscending { + timelineSortOrderSwitch.isOn = true + } else { + timelineSortOrderSwitch.isOn = false + } - // Do any additional setup after loading the view. - } - + refreshIntervalLabel.text = AppDefaults.refreshInterval.description() + } - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - } - */ + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + switch indexPath.section { + case 0: + print("Accounts isn't ready yet") + case 1: + switch indexPath.row { + case 0: + let timeline = UIStoryboard.settings.instantiateController(ofType: AboutViewController.self) + self.navigationController?.pushViewController(timeline, animated: true) + case 1: + UIApplication.shared.open(URL(string: "https://ranchero.com/netnewswire/")!, options: [:]) + case 2: + UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire")!, options: [:]) + case 3: + UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")!, options: [:]) + default: + UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")!, options: [:]) + } + case 2: + UIApplication.shared.open(URL(string: "https://appcamp4girls.com/contribute/")!, options: [:]) + default: + switch indexPath.row { + case 0: + let timeline = UIStoryboard.settings.instantiateController(ofType: RefreshIntervalViewController.self) + self.navigationController?.pushViewController(timeline, animated: true) + case 1: + addFeed() + case 2: + importOPML() + case 3: + exportOPML() + default: + print("export") + } + } + + } @IBAction func done(_ sender: Any) { dismiss(animated: true) } + @IBAction func switchTimelineOrder(_ sender: Any) { + if timelineSortOrderSwitch.isOn { + AppDefaults.timelineSortDirection = .orderedAscending + } else { + AppDefaults.timelineSortDirection = .orderedDescending + } + } + +} + +// MARK: OPML Document Picker + +extension SettingsViewController: UIDocumentPickerDelegate { + + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + + for url in urls { + do { + try OPMLImporter.parseAndImport(fileURL: url, account: AccountManager.shared.localAccount) + } catch { + presentError(title: "OPML Import Error", message: error.localizedDescription) + } + } + + } + +} + +// MARK: Private + +private extension SettingsViewController { + + func addFeed() { + + let appNewsURLString = "https://nnw.ranchero.com/feed.json" + if AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) { + presentError(title: "Subscribe", message: "You are already subscribed to the NetNewsWire news feed.") + return + } + + self.dismiss(animated: true) + + let addNavViewController = UIStoryboard.add.instantiateInitialViewController() as! UINavigationController + let addViewController = addNavViewController.topViewController as! AddContainerViewController + addNavViewController.modalPresentationStyle = .formSheet + addViewController.initialFeed = appNewsURLString + addViewController.initialFeedName = "NetNewsWire News" + + presentingParentController?.present(addNavViewController, animated: true) + + } + + func importOPML() { + + let docPicker = UIDocumentPickerViewController(documentTypes: ["public.xml", "org.opml.opml"], in: .import) + docPicker.delegate = self + docPicker.modalPresentationStyle = .formSheet + self.present(docPicker, animated: true) + + } + + func exportOPML() { + + let filename = "MySubscriptions.opml" + let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(filename) + let opmlString = OPMLExporter.OPMLString(with: AccountManager.shared.localAccount, title: filename) + do { + try opmlString.write(to: tempFile, atomically: true, encoding: String.Encoding.utf8) + } catch { + self.presentError(title: "OPML Export Error", message: error.localizedDescription) + } + + let docPicker = UIDocumentPickerViewController(url: tempFile, in: .exportToService) + docPicker.modalPresentationStyle = .formSheet + self.present(docPicker, animated: true) + + } + }