From 22553b661dbb7e4aa34876a9e2a801015a2d1288 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 7 Sep 2019 14:00:31 -0500 Subject: [PATCH 1/6] Begin to convert Settings to the latest SwiftUI --- NetNewsWire.xcodeproj/project.pbxproj | 4 +- iOS/SceneCoordinator.swift | 18 ++- iOS/Settings/SettingsView.swift | 208 +++++++++++++++----------- 3 files changed, 131 insertions(+), 99 deletions(-) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 289353912..3570e1595 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ 51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; + 519D73FB2323FF35008BB345 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F35D0822AFD4760003CE1B /* SettingsView.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; 51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -784,8 +785,8 @@ 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsReaderAPI.xib; sourceTree = ""; }; 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsReaderAPIWindowController.swift; sourceTree = ""; }; 5F323808231DF9F000706F6B /* NNWTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NNWTableViewCell.swift; sourceTree = ""; }; - 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 6543108B2322D90900658221 /* common */ = {isa = PBXFileReference; lastKnownFileType = folder; path = common; 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 = ""; }; 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionViewController.swift; sourceTree = ""; }; @@ -2515,6 +2516,7 @@ 51934CD023108953006127BE /* ActivityID.swift in Sources */, 51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */, 51C45259226508D300C03939 /* AppDefaults.swift in Sources */, + 519D73FB2323FF35008BB345 /* SettingsView.swift in Sources */, 511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */, 51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */, 51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */, diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index cc0cc47f8..e815d9170 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -6,7 +6,8 @@ // Copyright © 2019 Ranchero Software. All rights reserved. // -import Foundation +import UIKit +import SwiftUI import Account import Articles import RSCore @@ -852,14 +853,15 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func showSettings() { - let settingsNavViewController = UIStoryboard.settings.instantiateInitialViewController() as! UINavigationController - settingsNavViewController.modalPresentationStyle = .formSheet - let settingsViewController = settingsNavViewController.topViewController as! SettingsViewController - settingsViewController.presentingParentController = rootSplitViewController - rootSplitViewController.present(settingsNavViewController, animated: true) +// let settingsNavViewController = UIStoryboard.settings.instantiateInitialViewController() as! UINavigationController +// settingsNavViewController.modalPresentationStyle = .formSheet +// let settingsViewController = settingsNavViewController.topViewController as! SettingsViewController +// settingsViewController.presentingParentController = rootSplitViewController +// rootSplitViewController.present(settingsNavViewController, animated: true) - // let settings = UIHostingController(rootView: SettingsView(viewModel: SettingsView.ViewModel())) - // self.present(settings, animated: true) + let settings = UIHostingController(rootView: SettingsView(viewModel: SettingsView.ViewModel())) + settings.modalPresentationStyle = .formSheet + rootSplitViewController.present(settings, animated: true) } func showAdd(_ type: AddControllerType) { diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index bf7eacc1a..005f39e4f 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -11,26 +11,29 @@ import Combine import Account struct SettingsView : View { - @ObjectBinding var viewModel: ViewModel - @State var subscriptionsImportAccounts: ActionSheet? = nil - @State var subscriptionsImportDocumentPicker: Modal? = nil - @State var subscriptionsExportAccounts: ActionSheet? = nil - @State var subscriptionsExportDocumentPicker: Modal? = nil + + @ObservedObject var viewModel: ViewModel + + @State var isWebsitePresented: Bool = false + @State var isGithubRepoPresented: Bool = false + @State var isBugTrackerPresented: Bool = false + @State var isTechnotesPresented: Bool = false + @State var isHowToSupportPresented: Bool = false var body: some View { NavigationView { Form { - Section(header: Text("ACCOUNTS")) { - ForEach(viewModel.accounts.identified(by: \.self)) { account in - NavigationButton(destination: SettingsDetailAccountView(viewModel: SettingsDetailAccountView.ViewModel(account)), isDetail: false) { - Text(verbatim: account.nameForDisplay) - } - } - NavigationButton(destination: SettingsAddAccountView(), isDetail: false) { - Text("Add Account") - } - } +// Section(header: Text("ACCOUNTS")) { +// ForEach(viewModel.accounts.identified(by: \.self)) { account in +// NavigationLink(destination: SettingsDetailAccountView(viewModel: SettingsDetailAccountView.ViewModel(account)), isDetail: false) { +// Text(verbatim: account.nameForDisplay) +// } +// } +// NavigationLink(destination: SettingsAddAccountView(), isDetail: false) { +// Text("Add Account") +// } +// } Section(header: Text("TIMELINE")) { Toggle(isOn: $viewModel.sortOldestToNewest) { @@ -41,90 +44,115 @@ struct SettingsView : View { } } - Section(header: Text("DATABASE")) { - Picker(selection: $viewModel.refreshInterval, label: Text("Refresh Interval")) { - ForEach(RefreshInterval.allCases.identified(by: \.self)) { interval in - Text(interval.description()).tag(interval) - } - } - Button(action: { - self.subscriptionsImportAccounts = self.createSubscriptionsImportAccounts - }) { - Text("Import Subscriptions...") - } - .presentation(subscriptionsImportAccounts) - .presentation(subscriptionsImportDocumentPicker) - Button(action: { - self.subscriptionsExportAccounts = self.createSubscriptionsExportAccounts - }) { - Text("Export Subscriptions...") - } - .presentation(subscriptionsExportAccounts) - .presentation(subscriptionsExportDocumentPicker) - } - .foregroundColor(.primary) +// Section(header: Text("DATABASE")) { +// Picker(selection: $viewModel.refreshInterval, label: Text("Refresh Interval")) { +// ForEach(RefreshInterval.allCases.identified(by: \.self)) { interval in +// Text(interval.description()).tag(interval) +// } +// } +// Button(action: { +// self.subscriptionsImportAccounts = self.createSubscriptionsImportAccounts +// }) { +// Text("Import Subscriptions...") +// } +// .presentation(subscriptionsImportAccounts) +// .presentation(subscriptionsImportDocumentPicker) +// Button(action: { +// self.subscriptionsExportAccounts = self.createSubscriptionsExportAccounts +// }) { +// Text("Export Subscriptions...") +// } +// .presentation(subscriptionsExportAccounts) +// .presentation(subscriptionsExportDocumentPicker) +// } +// .foregroundColor(.primary) Section(header: Text("ABOUT"), footer: buildFooter) { Text("About NetNewsWire") - PresentationButton(destination: SafariView(url: URL(string: "https://ranchero.com/netnewswire/")!)) { + + Button(action: { self.isWebsitePresented.toggle() }) { Text("Website") + } + }.sheet(isPresented: $isWebsitePresented) { + SafariView(url: URL(string: "https://ranchero.com/netnewswire/")!) } - PresentationButton(destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire")!)) { - Text("Github Repository") + + VStack { + Button(action: { self.isGithubRepoPresented.toggle() }) { + Text("Github Repository") + } + }.sheet(isPresented: $isGithubRepoPresented) { + SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire")!) } - PresentationButton(destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")!)) { - Text("Bug Tracker") + + VStack { + Button(action: { self.isBugTrackerPresented.toggle() }) { + Text("Bug Tracker") + } + }.sheet(isPresented: $isBugTrackerPresented) { + SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")!) } - PresentationButton(destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")!)) { - Text("Technotes") + + VStack { + Button(action: { self.isTechnotesPresented.toggle() }) { + Text("Technotes") + } + }.sheet(isPresented: $isTechnotesPresented) { + SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")!) } - PresentationButton(destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown")!)) { - Text("How to Support NetNewsWire") + + VStack { + Button(action: { self.isHowToSupportPresented.toggle() }) { + Text("Technotes") + } + }.sheet(isPresented: $isHowToSupportPresented) { + SafariView(url: URL(string: "hhttps://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown")!) } + Text("Add NetNewsWire News Feed") - } - .foregroundColor(.primary) + + } + .foregroundColor(.primary) } .navigationBarTitle(Text("Settings"), displayMode: .inline) - } } - var createSubscriptionsImportAccounts: ActionSheet { - var buttons = [ActionSheet.Button]() - - for account in viewModel.activeAccounts { - if !account.isOPMLImportSupported { - continue - } - - let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { - self.subscriptionsImportAccounts = nil - self.subscriptionsImportDocumentPicker = Modal(SettingsSubscriptionsImportDocumentPickerView(account: account)) - } - - buttons.append(button) - } - - buttons.append(.cancel { self.subscriptionsImportAccounts = nil }) - return ActionSheet(title: Text("Import Subscriptions..."), message: Text("Select the account to import your OPML file into."), buttons: buttons) - } - - var createSubscriptionsExportAccounts: ActionSheet { - var buttons = [ActionSheet.Button]() - - for account in viewModel.accounts { - let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { - self.subscriptionsExportAccounts = nil - self.subscriptionsExportDocumentPicker = Modal(SettingsSubscriptionsExportDocumentPickerView(account: account)) - } - buttons.append(button) - } - - buttons.append(.cancel { self.subscriptionsExportAccounts = nil }) - return ActionSheet(title: Text("Export Subscriptions..."), message: Text("Select the account to export out of."), buttons: buttons) - } +// var createSubscriptionsImportAccounts: ActionSheet { +// var buttons = [ActionSheet.Button]() +// +// for account in viewModel.activeAccounts { +// if !account.isOPMLImportSupported { +// continue +// } +// +// let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { +// self.subscriptionsImportAccounts = nil +// self.subscriptionsImportDocumentPicker = Modal(SettingsSubscriptionsImportDocumentPickerView(account: account)) +// } +// +// buttons.append(button) +// } +// +// buttons.append(.cancel { self.subscriptionsImportAccounts = nil }) +// return ActionSheet(title: Text("Import Subscriptions..."), message: Text("Select the account to import your OPML file into."), buttons: buttons) +// } +// +// var createSubscriptionsExportAccounts: ActionSheet { +// var buttons = [ActionSheet.Button]() +// +// for account in viewModel.accounts { +// let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { +// self.subscriptionsExportAccounts = nil +// self.subscriptionsExportDocumentPicker = Modal(SettingsSubscriptionsExportDocumentPickerView(account: account)) +// } +// buttons.append(button) +// } +// +// buttons.append(.cancel { self.subscriptionsExportAccounts = nil }) +// return ActionSheet(title: Text("Export Subscriptions..."), message: Text("Select the account to export out of."), buttons: buttons) +// } var buildFooter: some View { return Text(verbatim: "\(Bundle.main.appName) v \(Bundle.main.versionNumber) (Build \(Bundle.main.buildNumber))") @@ -134,9 +162,9 @@ struct SettingsView : View { // MARK: ViewModel - class ViewModel: BindableObject { + class ViewModel: ObservableObject { - let didChange = PassthroughSubject() + let objectWillChange = ObservableObjectPublisher() init() { NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChange, object: nil) @@ -164,12 +192,12 @@ struct SettingsView : View { return AppDefaults.timelineSortDirection == .orderedDescending } set { + objectWillChange.send() if newValue == true { AppDefaults.timelineSortDirection = .orderedDescending } else { AppDefaults.timelineSortDirection = .orderedAscending } - didChange.send(self) } } @@ -178,8 +206,8 @@ struct SettingsView : View { return AppDefaults.timelineNumberOfLines } set { + objectWillChange.send() AppDefaults.timelineNumberOfLines = newValue - didChange.send(self) } } @@ -188,17 +216,17 @@ struct SettingsView : View { return AppDefaults.refreshInterval } set { + objectWillChange.send() AppDefaults.refreshInterval = newValue - didChange.send(self) } } @objc func accountsDidChange(_ notification: Notification) { - didChange.send(self) + objectWillChange.send() } @objc func displayNameDidChange(_ notification: Notification) { - didChange.send(self) + objectWillChange.send() } } From 3b4ec7262ff3217d009c6293faa64537e451be2a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 7 Sep 2019 14:10:20 -0500 Subject: [PATCH 2/6] Refactor website links to make code clearer and more concise --- iOS/Settings/SettingsView.swift | 59 +++++++++++++++------------------ 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index 005f39e4f..0bff1287f 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -15,10 +15,7 @@ struct SettingsView : View { @ObservedObject var viewModel: ViewModel @State var isWebsitePresented: Bool = false - @State var isGithubRepoPresented: Bool = false - @State var isBugTrackerPresented: Bool = false - @State var isTechnotesPresented: Bool = false - @State var isHowToSupportPresented: Bool = false + @State var website: String? = nil var body: some View { NavigationView { @@ -70,47 +67,45 @@ struct SettingsView : View { Section(header: Text("ABOUT"), footer: buildFooter) { Text("About NetNewsWire") - Button(action: { self.isWebsitePresented.toggle() }) { + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://ranchero.com/netnewswire/" + }) { Text("Website") - } - }.sheet(isPresented: $isWebsitePresented) { - SafariView(url: URL(string: "https://ranchero.com/netnewswire/")!) } - VStack { - Button(action: { self.isGithubRepoPresented.toggle() }) { - Text("Github Repository") - } - }.sheet(isPresented: $isGithubRepoPresented) { - SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire")!) + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire" + }) { + Text("Github Repository") } - VStack { - Button(action: { self.isBugTrackerPresented.toggle() }) { - Text("Bug Tracker") - } - }.sheet(isPresented: $isBugTrackerPresented) { - SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")!) + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire/issues" + }) { + Text("Bug Tracker") } - VStack { - Button(action: { self.isTechnotesPresented.toggle() }) { - Text("Technotes") - } - }.sheet(isPresented: $isTechnotesPresented) { - SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")!) + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes" + }) { + Text("Technotes") } - VStack { - Button(action: { self.isHowToSupportPresented.toggle() }) { - Text("Technotes") - } - }.sheet(isPresented: $isHowToSupportPresented) { - SafariView(url: URL(string: "hhttps://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown")!) + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown" + }) { + Text("How To Support NetNewsWire") } Text("Add NetNewsWire News Feed") + }.sheet(isPresented: $isWebsitePresented) { + SafariView(url: URL(string: self.website!)!) } .foregroundColor(.primary) From a4b30c2f0e03c918e61a6013886eb6b2eb68d413 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 7 Sep 2019 14:33:15 -0500 Subject: [PATCH 3/6] Add refresh interval to settings again --- NetNewsWire.xcodeproj/project.pbxproj | 12 ++++++++++++ .../RefreshInterval-Extensions.swift | 15 +++++++++++++++ iOS/Settings/SettingsView.swift | 16 ++++++++-------- 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 iOS/Model Extensions/RefreshInterval-Extensions.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 3570e1595..3d2e406a5 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 519D73FB2323FF35008BB345 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F35D0822AFD4760003CE1B /* SettingsView.swift */; }; + 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; 51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -727,6 +728,7 @@ 5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsImportDocumentPickerView.swift; sourceTree = ""; }; 5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsExportDocumentPickerView.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; + 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RefreshInterval-Extensions.swift"; sourceTree = ""; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = ""; }; 51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = ""; }; @@ -1129,6 +1131,14 @@ path = Wrappers; sourceTree = ""; }; + 519D740423243C68008BB345 /* Model Extensions */ = { + isa = PBXGroup; + children = ( + 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */, + ); + path = "Model Extensions"; + sourceTree = ""; + }; 51C45245226506C800C03939 /* Extensions */ = { isa = PBXGroup; children = ( @@ -1788,6 +1798,7 @@ 5183CCEB227117C70010922C /* Settings */, 5183CCDB226F1EEB0010922C /* Progress */, 51C45245226506C800C03939 /* Extensions */, + 519D740423243C68008BB345 /* Model Extensions */, 5F3237FF231DF9D000706F6B /* Views */, 5194B5E222B693EC00144881 /* Wrappers */, 84C9FC9A2262A1A900D921D6 /* Resources */, @@ -2505,6 +2516,7 @@ 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */, + 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */, 51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */, 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */, 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */, diff --git a/iOS/Model Extensions/RefreshInterval-Extensions.swift b/iOS/Model Extensions/RefreshInterval-Extensions.swift new file mode 100644 index 000000000..5906d10a2 --- /dev/null +++ b/iOS/Model Extensions/RefreshInterval-Extensions.swift @@ -0,0 +1,15 @@ +// +// RefreshInterval-Extensions.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 9/7/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation + +extension RefreshInterval: Identifiable { + var id: Int { + return rawValue + } +} diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index 0bff1287f..b5fbfa3ab 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -41,12 +41,12 @@ struct SettingsView : View { } } -// Section(header: Text("DATABASE")) { -// Picker(selection: $viewModel.refreshInterval, label: Text("Refresh Interval")) { -// ForEach(RefreshInterval.allCases.identified(by: \.self)) { interval in -// Text(interval.description()).tag(interval) -// } -// } + Section(header: Text("DATABASE")) { + Picker(selection: $viewModel.refreshInterval, label: Text("Refresh Interval")) { + ForEach(RefreshInterval.allCases) { interval in + Text(interval.description()).tag(interval) + } + } // Button(action: { // self.subscriptionsImportAccounts = self.createSubscriptionsImportAccounts // }) { @@ -61,8 +61,8 @@ struct SettingsView : View { // } // .presentation(subscriptionsExportAccounts) // .presentation(subscriptionsExportDocumentPicker) -// } -// .foregroundColor(.primary) + } + .foregroundColor(.primary) Section(header: Text("ABOUT"), footer: buildFooter) { Text("About NetNewsWire") From dcd2a9c4f61929b7277792414413332c3e5570dc Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 7 Sep 2019 15:30:54 -0500 Subject: [PATCH 4/6] Reimplement Import and Export OPML --- NetNewsWire.xcodeproj/project.pbxproj | 4 + iOS/Settings/SettingsView.swift | 108 ++++++++++++++------------ 2 files changed, 64 insertions(+), 48 deletions(-) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 3d2e406a5..a3db452f2 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -61,6 +61,8 @@ 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 519D73FB2323FF35008BB345 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F35D0822AFD4760003CE1B /* SettingsView.swift */; }; 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */; }; + 519D740723243FE7008BB345 /* SettingsSubscriptionsExportDocumentPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */; }; + 519D740823243FEA008BB345 /* SettingsSubscriptionsImportDocumentPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; 51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -2464,9 +2466,11 @@ 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */, 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, + 519D740823243FEA008BB345 /* SettingsSubscriptionsImportDocumentPickerView.swift in Sources */, 5183CCEF227125970010922C /* SettingsViewController.swift in Sources */, 51F85BE5227217D000C787DC /* RefreshIntervalViewController.swift in Sources */, 51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */, + 519D740723243FE7008BB345 /* SettingsSubscriptionsExportDocumentPickerView.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, 5183CCDF226F1FCC0010922C /* UINavigationController+Progress.swift in Sources */, 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */, diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index b5fbfa3ab..8ddb5dfdd 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -16,6 +16,12 @@ struct SettingsView : View { @State var isWebsitePresented: Bool = false @State var website: String? = nil + + @State var isOPMLImportPresented: Bool = false + @State var isOPMLImportDocPickerPresented: Bool = false + @State var isOPMLExportPresented: Bool = false + @State var isOPMLExportDocPickerPresented: Bool = false + @State var opmlAccount: Account? = nil var body: some View { NavigationView { @@ -47,20 +53,26 @@ struct SettingsView : View { Text(interval.description()).tag(interval) } } -// Button(action: { -// self.subscriptionsImportAccounts = self.createSubscriptionsImportAccounts -// }) { -// Text("Import Subscriptions...") -// } -// .presentation(subscriptionsImportAccounts) -// .presentation(subscriptionsImportDocumentPicker) -// Button(action: { -// self.subscriptionsExportAccounts = self.createSubscriptionsExportAccounts -// }) { -// Text("Export Subscriptions...") -// } -// .presentation(subscriptionsExportAccounts) -// .presentation(subscriptionsExportDocumentPicker) + + VStack { + Button("Import Subscriptions...") { + self.isOPMLImportPresented = true + } + }.actionSheet(isPresented: $isOPMLImportPresented) { + createSubscriptionsImportAccounts + }.sheet(isPresented: $isOPMLImportDocPickerPresented) { + SettingsSubscriptionsImportDocumentPickerView(account: self.opmlAccount!) + } + + VStack { + Button("Export Subscriptions...") { + self.isOPMLExportPresented = true + } + }.actionSheet(isPresented: $isOPMLExportPresented) { + createSubscriptionsExportAccounts + }.sheet(isPresented: $isOPMLExportDocPickerPresented) { + SettingsSubscriptionsExportDocumentPickerView(account: self.opmlAccount!) + } } .foregroundColor(.primary) @@ -114,40 +126,40 @@ struct SettingsView : View { } } -// var createSubscriptionsImportAccounts: ActionSheet { -// var buttons = [ActionSheet.Button]() -// -// for account in viewModel.activeAccounts { -// if !account.isOPMLImportSupported { -// continue -// } -// -// let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { -// self.subscriptionsImportAccounts = nil -// self.subscriptionsImportDocumentPicker = Modal(SettingsSubscriptionsImportDocumentPickerView(account: account)) -// } -// -// buttons.append(button) -// } -// -// buttons.append(.cancel { self.subscriptionsImportAccounts = nil }) -// return ActionSheet(title: Text("Import Subscriptions..."), message: Text("Select the account to import your OPML file into."), buttons: buttons) -// } -// -// var createSubscriptionsExportAccounts: ActionSheet { -// var buttons = [ActionSheet.Button]() -// -// for account in viewModel.accounts { -// let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { -// self.subscriptionsExportAccounts = nil -// self.subscriptionsExportDocumentPicker = Modal(SettingsSubscriptionsExportDocumentPickerView(account: account)) -// } -// buttons.append(button) -// } -// -// buttons.append(.cancel { self.subscriptionsExportAccounts = nil }) -// return ActionSheet(title: Text("Export Subscriptions..."), message: Text("Select the account to export out of."), buttons: buttons) -// } + var createSubscriptionsImportAccounts: ActionSheet { + var buttons = [ActionSheet.Button]() + + for account in viewModel.activeAccounts { + if !account.isOPMLImportSupported { + continue + } + + let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { + self.opmlAccount = account + self.isOPMLImportDocPickerPresented = true + } + + buttons.append(button) + } + + buttons.append(.cancel()) + return ActionSheet(title: Text("Import Subscriptions..."), message: Text("Select the account to import your OPML file into."), buttons: buttons) + } + + var createSubscriptionsExportAccounts: ActionSheet { + var buttons = [ActionSheet.Button]() + + for account in viewModel.accounts { + let button = ActionSheet.Button.default(Text(verbatim: account.nameForDisplay)) { + self.opmlAccount = account + self.isOPMLExportDocPickerPresented = true + } + buttons.append(button) + } + + buttons.append(.cancel()) + return ActionSheet(title: Text("Export Subscriptions..."), message: Text("Select the account to export out of."), buttons: buttons) + } var buildFooter: some View { return Text(verbatim: "\(Bundle.main.appName) v \(Bundle.main.versionNumber) (Build \(Bundle.main.buildNumber))") From a01b9ebe73a373b96776b5a3ebda31417ebcd700 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 7 Sep 2019 16:43:44 -0500 Subject: [PATCH 5/6] Add Done button to Setting modal --- iOS/SceneCoordinator.swift | 6 +++--- iOS/Settings/SettingsView.swift | 18 +++++++++++------- submodules/RSCore | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index e815d9170..2953669ac 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -859,9 +859,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { // settingsViewController.presentingParentController = rootSplitViewController // rootSplitViewController.present(settingsNavViewController, animated: true) - let settings = UIHostingController(rootView: SettingsView(viewModel: SettingsView.ViewModel())) - settings.modalPresentationStyle = .formSheet - rootSplitViewController.present(settings, animated: true) + rootSplitViewController.present(style: .formSheet) { + SettingsView(viewModel: SettingsView.ViewModel()) + } } func showAdd(_ type: AddControllerType) { diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index 8ddb5dfdd..a80462769 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -10,18 +10,21 @@ import SwiftUI import Combine import Account + struct SettingsView : View { @ObservedObject var viewModel: ViewModel + + @Environment(\.viewController) private var viewController: UIViewController? - @State var isWebsitePresented: Bool = false - @State var website: String? = nil + @State private var isWebsitePresented: Bool = false + @State private var website: String? = nil - @State var isOPMLImportPresented: Bool = false - @State var isOPMLImportDocPickerPresented: Bool = false - @State var isOPMLExportPresented: Bool = false - @State var isOPMLExportDocPickerPresented: Bool = false - @State var opmlAccount: Account? = nil + @State private var isOPMLImportPresented: Bool = false + @State private var isOPMLImportDocPickerPresented: Bool = false + @State private var isOPMLExportPresented: Bool = false + @State private var isOPMLExportDocPickerPresented: Bool = false + @State private var opmlAccount: Account? = nil var body: some View { NavigationView { @@ -123,6 +126,7 @@ struct SettingsView : View { } .navigationBarTitle(Text("Settings"), displayMode: .inline) + .navigationBarItems(leading: Button(action: { self.viewController?.dismiss(animated: true) }) { Text("Done") } ) } } diff --git a/submodules/RSCore b/submodules/RSCore index 7a4895657..3e0dbb1c0 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 7a48956576a242c631d634520748de326d893f9b +Subproject commit 3e0dbb1c0a88697e7be510da0226fe1e3e7ef195 From fe874f3ca7734f70a6a3ba9b07a309a5b27756f7 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 7 Sep 2019 20:50:57 -0500 Subject: [PATCH 6/6] Update Settings for Accounts to work with the latest SwiftUI --- NetNewsWire.xcodeproj/project.pbxproj | 14 ++ iOS/Model Extensions/Account-Extensions.swift | 16 ++ iOS/Settings/SettingsAccountLabelView.swift | 18 +- iOS/Settings/SettingsAddAccountView.swift | 29 ++- iOS/Settings/SettingsDetailAccountView.swift | 50 ++--- iOS/Settings/SettingsFeedbinAccountView.swift | 45 ++-- iOS/Settings/SettingsLocalAccountView.swift | 8 +- iOS/Settings/SettingsView.swift | 205 +++++++++--------- 8 files changed, 215 insertions(+), 170 deletions(-) create mode 100644 iOS/Model Extensions/Account-Extensions.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index a3db452f2..1b91251a2 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -34,6 +34,8 @@ 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; }; 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */; }; + 5152E0F923248F6200E5C7AD /* SettingsLocalAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */; }; + 5152E1022324900D00E5C7AD /* SettingsAddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707322B028E1004E8F65 /* SettingsAddAccountView.swift */; }; 51543685228F6753005E1CDF /* DetailAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51543684228F6753005E1CDF /* DetailAccountViewController.swift */; }; 515436882291D75D005E1CDF /* AddLocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515436872291D75D005E1CDF /* AddLocalAccountViewController.swift */; }; 5154368A2291FED9005E1CDF /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515436892291FED9005E1CDF /* FeedbinAccountViewController.swift */; }; @@ -64,6 +66,10 @@ 519D740723243FE7008BB345 /* SettingsSubscriptionsExportDocumentPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */; }; 519D740823243FEA008BB345 /* SettingsSubscriptionsImportDocumentPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; + 51AF45E123246731001742EF /* SettingsAccountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */; }; + 51AF460323247321001742EF /* SettingsDetailAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */; }; + 51AF460C23247F11001742EF /* SettingsFeedbinAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */; }; + 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AF460D232488C6001742EF /* Account-Extensions.swift */; }; 51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451B9226377C900C03939 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; }; @@ -732,6 +738,7 @@ 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RefreshInterval-Extensions.swift"; sourceTree = ""; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = ""; }; 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = ""; }; 51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = ""; }; 51C45254226507D200C03939 /* AppAssets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = ""; }; @@ -1137,6 +1144,7 @@ isa = PBXGroup; children = ( 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */, + 51AF460D232488C6001742EF /* Account-Extensions.swift */, ); path = "Model Extensions"; sourceTree = ""; @@ -2443,6 +2451,7 @@ 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */, 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */, 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */, + 5152E1022324900D00E5C7AD /* SettingsAddAccountView.swift in Sources */, 515ADE4022E11FAE006B2460 /* SystemMessageViewController.swift in Sources */, 51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */, 51C45296226509D300C03939 /* OPMLExporter.swift in Sources */, @@ -2451,12 +2460,15 @@ 51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */, 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, + 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */, 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */, + 51AF45E123246731001742EF /* SettingsAccountLabelView.swift in Sources */, 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */, 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */, 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */, + 5152E0F923248F6200E5C7AD /* SettingsLocalAccountView.swift in Sources */, 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51C452852265093600C03939 /* AddFeedFolderPickerData.swift in Sources */, @@ -2469,6 +2481,7 @@ 519D740823243FEA008BB345 /* SettingsSubscriptionsImportDocumentPickerView.swift in Sources */, 5183CCEF227125970010922C /* SettingsViewController.swift in Sources */, 51F85BE5227217D000C787DC /* RefreshIntervalViewController.swift in Sources */, + 51AF460C23247F11001742EF /* SettingsFeedbinAccountView.swift in Sources */, 51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */, 519D740723243FE7008BB345 /* SettingsSubscriptionsExportDocumentPickerView.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, @@ -2506,6 +2519,7 @@ 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 51C45290226509C100C03939 /* PseudoFeed.swift in Sources */, 51C452A922650DC600C03939 /* ArticleRenderer.swift in Sources */, + 51AF460323247321001742EF /* SettingsDetailAccountView.swift in Sources */, 5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */, 51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */, 512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */, diff --git a/iOS/Model Extensions/Account-Extensions.swift b/iOS/Model Extensions/Account-Extensions.swift new file mode 100644 index 000000000..9574f3553 --- /dev/null +++ b/iOS/Model Extensions/Account-Extensions.swift @@ -0,0 +1,16 @@ +// +// Account-Extensions.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 9/7/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import Account + +extension Account: Identifiable { + public var id: String { + return accountID + } +} diff --git a/iOS/Settings/SettingsAccountLabelView.swift b/iOS/Settings/SettingsAccountLabelView.swift index 8c7f6d41f..0d5ead2e3 100644 --- a/iOS/Settings/SettingsAccountLabelView.swift +++ b/iOS/Settings/SettingsAccountLabelView.swift @@ -14,19 +14,13 @@ struct SettingsAccountLabelView : View { var body: some View { HStack { - Spacer() - HStack { - Image(accountImage) - .resizable() - .aspectRatio(1, contentMode: .fit) - .frame(height: 32) - Text(verbatim: accountLabel).font(.title) - - } - .layoutPriority(1) - Spacer() + Image(accountImage) + .resizable() + .aspectRatio(1, contentMode: .fit) + .frame(height: 32) + Text(verbatim: accountLabel).font(.title) } - .foregroundColor(.primary) + .foregroundColor(.primary).padding(4.0) } } diff --git a/iOS/Settings/SettingsAddAccountView.swift b/iOS/Settings/SettingsAddAccountView.swift index 22fd2ca7c..10f1b7108 100644 --- a/iOS/Settings/SettingsAddAccountView.swift +++ b/iOS/Settings/SettingsAddAccountView.swift @@ -10,17 +10,38 @@ import SwiftUI import Account struct SettingsAddAccountView : View { + + @State private var isAddPresented = false + @State private var selectedAccountType: AccountType = nil + var body: some View { Form { - PresentationButton(destination: SettingsLocalAccountView(name: "")) { + + Button(action: { + self.selectedAccountType = AccountType.onMyMac + self.isAddPresented.toggle() + }) { SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: Account.defaultLocalAccountName) } - .padding(4) - PresentationButton(destination: SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel())) { + + Button(action: { + self.selectedAccountType = AccountType.feedbin + self.isAddPresented.toggle() + }) { SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin") } - .padding(4) + + } + .sheet(isPresented: $isAddPresented) { + if self.selectedAccountType == .onMyMac { + SettingsLocalAccountView(name: "") + } + if self.selectedAccountType == .feedbin { + SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel()) + } + } + .navigationBarTitle(Text("Add Account"), displayMode: .inline) } } diff --git a/iOS/Settings/SettingsDetailAccountView.swift b/iOS/Settings/SettingsDetailAccountView.swift index 8bc4d5edf..b97177f35 100644 --- a/iOS/Settings/SettingsDetailAccountView.swift +++ b/iOS/Settings/SettingsDetailAccountView.swift @@ -12,17 +12,15 @@ import Account import RSWeb struct SettingsDetailAccountView : View { - @ObjectBinding var viewModel: ViewModel - @State private var verifyDelete = false - @State private var showFeedbinCredentials = false - + @ObservedObject var viewModel: ViewModel + @State private var isFeedbinCredentialsPresented = false + @State private var isDeleteAlertPresented = false + var body: some View { Form { Section { HStack { - Text("Name") - Divider() - TextField($viewModel.name, placeholder: Text("(Optional)")) + TextField("Name", text: $viewModel.name) } Toggle(isOn: $viewModel.isActive) { Text("Active") @@ -33,33 +31,33 @@ struct SettingsDetailAccountView : View { HStack { Spacer() Button(action: { - self.showFeedbinCredentials = true + self.isFeedbinCredentialsPresented.toggle() }) { Text("Credentials") } - .presentation(showFeedbinCredentials ? feedbinCredentialsModal : nil) - .onDisappear() { self.showFeedbinCredentials = false } Spacer() } } + .sheet(isPresented: $isFeedbinCredentialsPresented) { + self.settingsFeedbinAccountView + } } if viewModel.isDeletable { Section { HStack { Spacer() Button(action: { - self.verifyDelete = true + self.isDeleteAlertPresented.toggle() }) { - Text("Delete Account") - .foregroundColor(.red) - } - .presentation($verifyDelete) { - Alert(title: Text("Are you sure you want to delete \"\(viewModel.nameForDisplay)\"?"), - primaryButton: Alert.Button.default(Text("Delete"), onTrigger: { self.viewModel.delete() }), - secondaryButton: Alert.Button.cancel()) + Text("Delete Account").foregroundColor(.red) } Spacer() } + .alert(isPresented: $isDeleteAlertPresented) { + Alert(title: Text("Are you sure you want to delete \"\(viewModel.nameForDisplay)\"?"), + primaryButton: Alert.Button.default(Text("Delete"), action: { self.viewModel.delete() }), + secondaryButton: Alert.Button.cancel()) + } } } } @@ -67,13 +65,15 @@ struct SettingsDetailAccountView : View { } - var feedbinCredentialsModal: Modal { + var settingsFeedbinAccountView: SettingsFeedbinAccountView { let feedbinViewModel = SettingsFeedbinAccountView.ViewModel(account: viewModel.account) - return Modal(SettingsFeedbinAccountView(viewModel: feedbinViewModel)) + return SettingsFeedbinAccountView(viewModel: feedbinViewModel) } - class ViewModel: BindableObject { - let didChange = PassthroughSubject() + class ViewModel: ObservableObject { + + let objectWillChange = ObservableObjectPublisher() + let account: Account init(_ account: Account) { @@ -89,8 +89,8 @@ struct SettingsDetailAccountView : View { account.name ?? "" } set { + objectWillChange.send() account.name = newValue.isEmpty ? nil : newValue - didChange.send(self) } } @@ -99,8 +99,8 @@ struct SettingsDetailAccountView : View { account.isActive } set { + objectWillChange.send() account.isActive = newValue - didChange.send(self) } } @@ -114,7 +114,7 @@ struct SettingsDetailAccountView : View { func delete() { AccountManager.shared.deleteAccount(account) - ActivityManager.shared.cleanUp(account) +// ActivityManager.shared.cleanUp(account) } } } diff --git a/iOS/Settings/SettingsFeedbinAccountView.swift b/iOS/Settings/SettingsFeedbinAccountView.swift index 62e88069a..00ef485b7 100644 --- a/iOS/Settings/SettingsFeedbinAccountView.swift +++ b/iOS/Settings/SettingsFeedbinAccountView.swift @@ -12,10 +12,10 @@ import Account import RSWeb struct SettingsFeedbinAccountView : View { - @Environment(\.isPresented) private var isPresented - @ObjectBinding var viewModel: ViewModel + @Environment(\.presentationMode) var presentation + @ObservedObject var viewModel: ViewModel @State var busy: Bool = false - @State var error: Text = Text("") + @State var error: String = "" var body: some View { NavigationView { @@ -23,22 +23,13 @@ struct SettingsFeedbinAccountView : View { Section(header: SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin").padding() ) { - HStack { - Text("Email:") - Divider() - TextField($viewModel.email) - .textContentType(.username) - } - HStack { - Text("Password:") - Divider() - SecureField($viewModel.password) - } + TextField("Email", text: $viewModel.email).textContentType(.emailAddress) + SecureField("Password", text: $viewModel.password) } Section(footer: HStack { Spacer() - error.color(.red) + Text(verbatim: error).foregroundColor(.red) Spacer() } ) { @@ -67,7 +58,7 @@ struct SettingsFeedbinAccountView : View { private func addAccount() { busy = true - error = Text("") + error = "" let emailAddress = viewModel.email.trimmingCharacters(in: .whitespaces) let credentials = Credentials.basic(username: emailAddress, password: viewModel.password) @@ -104,15 +95,15 @@ struct SettingsFeedbinAccountView : View { self.dismiss() } catch { - self.error = Text("Keychain error while storing credentials.") + self.error = "Keychain error while storing credentials." } } else { - self.error = Text("Invalid email/password combination.") + self.error = "Invalid email/password combination." } case .failure: - self.error = Text("Network error. Try again later.") + self.error = "Network error. Try again later." } } @@ -120,11 +111,12 @@ struct SettingsFeedbinAccountView : View { } private func dismiss() { - isPresented?.value = false + presentation.wrappedValue.dismiss() } - class ViewModel: BindableObject { - let didChange = PassthroughSubject() + class ViewModel: ObservableObject { + + let objectWillChange = ObservableObjectPublisher() var account: Account? = nil init() { @@ -139,13 +131,14 @@ struct SettingsFeedbinAccountView : View { } var email: String = "" { - didSet { - didChange.send(self) + willSet { + objectWillChange.send() } } + var password: String = "" { - didSet { - didChange.send(self) + willSet { + objectWillChange.send() } } diff --git a/iOS/Settings/SettingsLocalAccountView.swift b/iOS/Settings/SettingsLocalAccountView.swift index 0c2b68569..c54e65cf8 100644 --- a/iOS/Settings/SettingsLocalAccountView.swift +++ b/iOS/Settings/SettingsLocalAccountView.swift @@ -10,7 +10,7 @@ import SwiftUI import Account struct SettingsLocalAccountView : View { - @Environment(\.isPresented) private var isPresented + @Environment(\.presentationMode) var presentation @State var name: String var body: some View { @@ -20,9 +20,7 @@ struct SettingsLocalAccountView : View { SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: Account.defaultLocalAccountName).padding() ) { HStack { - Text("Name") - Divider() - TextField($name, placeholder: Text("(Optional)")) + TextField("Name", text: $name) } } Section { @@ -47,7 +45,7 @@ struct SettingsLocalAccountView : View { } private func dismiss() { - isPresented?.value = false + presentation.wrappedValue.dismiss() } } diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index a80462769..c6b927476 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -29,108 +29,117 @@ struct SettingsView : View { var body: some View { NavigationView { Form { - -// Section(header: Text("ACCOUNTS")) { -// ForEach(viewModel.accounts.identified(by: \.self)) { account in -// NavigationLink(destination: SettingsDetailAccountView(viewModel: SettingsDetailAccountView.ViewModel(account)), isDetail: false) { -// Text(verbatim: account.nameForDisplay) -// } -// } -// NavigationLink(destination: SettingsAddAccountView(), isDetail: false) { -// Text("Add Account") -// } -// } - - Section(header: Text("TIMELINE")) { - Toggle(isOn: $viewModel.sortOldestToNewest) { - Text("Sort Oldest to Newest") - } - Stepper(value: $viewModel.timelineNumberOfLines, in: 2...6) { - Text("Number of Text Lines: \(viewModel.timelineNumberOfLines)") - } - } - - Section(header: Text("DATABASE")) { - Picker(selection: $viewModel.refreshInterval, label: Text("Refresh Interval")) { - ForEach(RefreshInterval.allCases) { interval in - Text(interval.description()).tag(interval) - } - } - - VStack { - Button("Import Subscriptions...") { - self.isOPMLImportPresented = true - } - }.actionSheet(isPresented: $isOPMLImportPresented) { - createSubscriptionsImportAccounts - }.sheet(isPresented: $isOPMLImportDocPickerPresented) { - SettingsSubscriptionsImportDocumentPickerView(account: self.opmlAccount!) - } - - VStack { - Button("Export Subscriptions...") { - self.isOPMLExportPresented = true - } - }.actionSheet(isPresented: $isOPMLExportPresented) { - createSubscriptionsExportAccounts - }.sheet(isPresented: $isOPMLExportDocPickerPresented) { - SettingsSubscriptionsExportDocumentPickerView(account: self.opmlAccount!) - } - } - .foregroundColor(.primary) - - Section(header: Text("ABOUT"), footer: buildFooter) { - Text("About NetNewsWire") - - Button(action: { - self.isWebsitePresented.toggle() - self.website = "https://ranchero.com/netnewswire/" - }) { - Text("Website") - } - - Button(action: { - self.isWebsitePresented.toggle() - self.website = "https://github.com/brentsimmons/NetNewsWire" - }) { - Text("Github Repository") - } - - Button(action: { - self.isWebsitePresented.toggle() - self.website = "https://github.com/brentsimmons/NetNewsWire/issues" - }) { - Text("Bug Tracker") - } - - Button(action: { - self.isWebsitePresented.toggle() - self.website = "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes" - }) { - Text("Technotes") - } - - Button(action: { - self.isWebsitePresented.toggle() - self.website = "https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown" - }) { - Text("How To Support NetNewsWire") - } - - Text("Add NetNewsWire News Feed") - - }.sheet(isPresented: $isWebsitePresented) { - SafariView(url: URL(string: self.website!)!) - } - .foregroundColor(.primary) - + buildAccountsSection() + buildTimelineSection() + buildDatabaseSection() + buildAboutSection() } .navigationBarTitle(Text("Settings"), displayMode: .inline) .navigationBarItems(leading: Button(action: { self.viewController?.dismiss(animated: true) }) { Text("Done") } ) } } - var createSubscriptionsImportAccounts: ActionSheet { + func buildAccountsSection() -> some View { + Section(header: Text("ACCOUNTS")) { + ForEach(viewModel.accounts) { account in + NavigationLink(destination: SettingsDetailAccountView(viewModel: SettingsDetailAccountView.ViewModel(account))) { + Text(verbatim: account.nameForDisplay) + } + } + NavigationLink(destination: SettingsAddAccountView()) { + Text("Add Account") + } + } + } + + func buildTimelineSection() -> some View { + Section(header: Text("TIMELINE")) { + Toggle(isOn: $viewModel.sortOldestToNewest) { + Text("Sort Oldest to Newest") + } + Stepper(value: $viewModel.timelineNumberOfLines, in: 2...6) { + Text("Number of Text Lines: \(viewModel.timelineNumberOfLines)") + } + } + } + + func buildDatabaseSection() -> some View { + Section(header: Text("DATABASE")) { + Picker(selection: $viewModel.refreshInterval, label: Text("Refresh Interval")) { + ForEach(RefreshInterval.allCases) { interval in + Text(interval.description()).tag(interval) + } + } + + VStack { + Button("Import Subscriptions...") { + self.isOPMLImportPresented = true + } + }.actionSheet(isPresented: $isOPMLImportPresented) { + buildSubscriptionsImportAccounts() + }.sheet(isPresented: $isOPMLImportDocPickerPresented) { + SettingsSubscriptionsImportDocumentPickerView(account: self.opmlAccount!) + }.foregroundColor(.primary) + + VStack { + Button("Export Subscriptions...") { + self.isOPMLExportPresented = true + } + }.actionSheet(isPresented: $isOPMLExportPresented) { + buildSubscriptionsExportAccounts() + }.sheet(isPresented: $isOPMLExportDocPickerPresented) { + SettingsSubscriptionsExportDocumentPickerView(account: self.opmlAccount!) + }.foregroundColor(.primary) + } + } + + func buildAboutSection() -> some View { + Section(header: Text("ABOUT"), footer: buildFooter()) { + Text("About NetNewsWire") + + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://ranchero.com/netnewswire/" + }) { + Text("Website") + }.foregroundColor(.primary) + + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire" + }) { + Text("Github Repository") + }.foregroundColor(.primary) + + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire/issues" + }) { + Text("Bug Tracker") + }.foregroundColor(.primary) + + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes" + }) { + Text("Technotes") + }.foregroundColor(.primary) + + Button(action: { + self.isWebsitePresented.toggle() + self.website = "https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown" + }) { + Text("How To Support NetNewsWire") + }.foregroundColor(.primary) + + Text("Add NetNewsWire News Feed") + + }.sheet(isPresented: $isWebsitePresented) { + SafariView(url: URL(string: self.website!)!) + } + } + + func buildSubscriptionsImportAccounts() -> ActionSheet { var buttons = [ActionSheet.Button]() for account in viewModel.activeAccounts { @@ -150,7 +159,7 @@ struct SettingsView : View { return ActionSheet(title: Text("Import Subscriptions..."), message: Text("Select the account to import your OPML file into."), buttons: buttons) } - var createSubscriptionsExportAccounts: ActionSheet { + func buildSubscriptionsExportAccounts() -> ActionSheet { var buttons = [ActionSheet.Button]() for account in viewModel.accounts { @@ -165,7 +174,7 @@ struct SettingsView : View { return ActionSheet(title: Text("Export Subscriptions..."), message: Text("Select the account to export out of."), buttons: buttons) } - var buildFooter: some View { + func buildFooter() -> some View { return Text(verbatim: "\(Bundle.main.appName) v \(Bundle.main.versionNumber) (Build \(Bundle.main.buildNumber))") .font(.footnote) .foregroundColor(.secondary)