diff --git a/Mac/Resources/Info.plist b/Mac/Resources/Info.plist index 579e08015..c6dc652a0 100644 --- a/Mac/Resources/Info.plist +++ b/Mac/Resources/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.0a3 + 5.0a4 CFBundleURLTypes diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 275f9fb09..414f74d58 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -49,6 +49,8 @@ 5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; 5183CCED22711DCE0010922C /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5183CCEC22711DCE0010922C /* Settings.storyboard */; }; 5183CCEF227125970010922C /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCEE227125970010922C /* SettingsViewController.swift */; }; + 5194B5EE22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */; }; + 5194B5F222B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.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, ); }; }; @@ -320,6 +322,7 @@ D5F4EDB5200744A700B9E363 /* ScriptingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */; }; D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */; }; D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; }; + DF999FF722B5AEFA0064B687 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF999FF622B5AEFA0064B687 /* SafariView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -695,6 +698,8 @@ 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountRefreshTimer.swift; sourceTree = ""; }; 5183CCEC22711DCE0010922C /* Settings.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; 5183CCEE227125970010922C /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; + 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 = ""; }; 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 = ""; }; @@ -930,6 +935,7 @@ D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptingObject.swift; sourceTree = ""; }; D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feed+Scriptability.swift"; sourceTree = ""; }; D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Folder+Scriptability.swift"; sourceTree = ""; }; + DF999FF622B5AEFA0064B687 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1049,12 +1055,22 @@ 51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */, 510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */, 510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */, + 5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */, + 5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */, 51F35D0822AFD4760003CE1B /* SettingsView.swift */, 51F35CFD22AFD0350003CE1B /* UIKit */, ); path = Settings; sourceTree = ""; }; + 5194B5E222B693EC00144881 /* Wrappers */ = { + isa = PBXGroup; + children = ( + DF999FF622B5AEFA0064B687 /* SafariView.swift */, + ); + path = Wrappers; + sourceTree = ""; + }; 51C45245226506C800C03939 /* Extensions */ = { isa = PBXGroup; children = ( @@ -1696,6 +1712,7 @@ 5183CCEB227117C70010922C /* Settings */, 5183CCDB226F1EEB0010922C /* Progress */, 51C45245226506C800C03939 /* Extensions */, + 5194B5E222B693EC00144881 /* Wrappers */, 84C9FC9A2262A1A900D921D6 /* Resources */, ); path = iOS; @@ -2372,10 +2389,13 @@ 51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */, 5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */, 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */, + DF999FF722B5AEFA0064B687 /* SafariView.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 5183CCE3226F314C0010922C /* ProgressTableViewController.swift in Sources */, 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */, + 5194B5F222B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift in Sources */, 51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */, + 5194B5EE22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift in Sources */, 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */, 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */, 51E595AD228E1C2100FCC42B /* AddAccountViewController.swift in Sources */, diff --git a/iOS/Settings/SettingsDetailAccountView.swift b/iOS/Settings/SettingsDetailAccountView.swift index 75f4fe1fe..dab8f30ce 100644 --- a/iOS/Settings/SettingsDetailAccountView.swift +++ b/iOS/Settings/SettingsDetailAccountView.swift @@ -9,10 +9,12 @@ import SwiftUI import Combine import Account +import RSWeb struct SettingsDetailAccountView : View { @ObjectBinding var viewModel: ViewModel @State private var verifyDelete = false + @State private var showFeedbinCredentials = false var body: some View { List { @@ -26,15 +28,19 @@ struct SettingsDetailAccountView : View { Text("Active") } } - Section { - HStack { - Spacer() - Button(action: { - - }) { - Text("Credentials") + if viewModel.isCreditialsAvailable { + Section { + HStack { + Spacer() + Button(action: { + self.showFeedbinCredentials = true + }) { + Text("Credentials") + } + .presentation(showFeedbinCredentials ? feedbinCredentialsModal : nil) + .onDisappear() { self.showFeedbinCredentials = false } + Spacer() } - Spacer() } } if viewModel.isDeletable { @@ -62,6 +68,11 @@ struct SettingsDetailAccountView : View { } + var feedbinCredentialsModal: Modal { + let feedbinViewModel = SettingsFeedbinAccountView.ViewModel(account: viewModel.account) + return Modal(SettingsFeedbinAccountView(viewModel: feedbinViewModel)) + } + class ViewModel: BindableObject { let didChange = PassthroughSubject() let account: Account @@ -94,6 +105,10 @@ struct SettingsDetailAccountView : View { } } + var isCreditialsAvailable: Bool { + return account.type != .onMyMac + } + var isDeletable: Bool { return AccountManager.shared.defaultAccount != account } diff --git a/iOS/Settings/SettingsFeedbinAccountView.swift b/iOS/Settings/SettingsFeedbinAccountView.swift index 50d425bc7..c867c94d2 100644 --- a/iOS/Settings/SettingsFeedbinAccountView.swift +++ b/iOS/Settings/SettingsFeedbinAccountView.swift @@ -16,8 +16,7 @@ struct SettingsFeedbinAccountView : View { @ObjectBinding var viewModel: ViewModel @State var busy: Bool = false @State var error: Text = Text("") - var account: Account? = nil - + var body: some View { NavigationView { List { @@ -25,15 +24,15 @@ struct SettingsFeedbinAccountView : View { SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin").padding() ) { HStack { - Spacer() - TextField($viewModel.email, placeholder: Text("Email")) - .textContentType(.username) - Spacer() + Text("Email:") + Divider() + TextField($viewModel.email) + .textContentType(.username) } HStack { - Spacer() - SecureField($viewModel.password, placeholder: Text("Password")) - Spacer() + Text("Password:") + Divider() + SecureField($viewModel.password) } } Section(footer: @@ -46,7 +45,11 @@ struct SettingsFeedbinAccountView : View { HStack { Spacer() Button(action: { self.addAccount() }) { - Text("Add Account") + if viewModel.isUpdate { + Text("Update Account") + } else { + Text("Add Account") + } } .disabled(!viewModel.isValid) Spacer() @@ -65,7 +68,8 @@ struct SettingsFeedbinAccountView : View { private func addAccount() { busy = true - + error = Text("") + let emailAddress = viewModel.email.trimmingCharacters(in: .whitespaces) let credentials = Credentials.basic(username: emailAddress, password: viewModel.password) @@ -80,11 +84,11 @@ struct SettingsFeedbinAccountView : View { var newAccount = false let workAccount: Account - if self.account == nil { + if self.viewModel.account == nil { workAccount = AccountManager.shared.createAccount(type: .feedbin) newAccount = true } else { - workAccount = self.account! + workAccount = self.viewModel.account! } do { @@ -122,6 +126,18 @@ struct SettingsFeedbinAccountView : View { class ViewModel: BindableObject { let didChange = PassthroughSubject() + var account: Account? = nil + + init() { + } + + init(account: Account) { + self.account = account + if case .basic(let username, let password) = try? account.retrieveBasicCredentials() { + self.email = username + self.password = password + } + } var email: String = "" { didSet { @@ -134,6 +150,10 @@ struct SettingsFeedbinAccountView : View { } } + var isUpdate: Bool { + return account != nil + } + var isValid: Bool { return !email.isEmpty && !password.isEmpty } @@ -144,7 +164,7 @@ struct SettingsFeedbinAccountView : View { #if DEBUG struct SettingsFeedbinAccountView_Previews : PreviewProvider { static var previews: some View { - SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel()) + SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel()) } } #endif diff --git a/iOS/Settings/SettingsLocalAccountView.swift b/iOS/Settings/SettingsLocalAccountView.swift index 40f671661..6247aea77 100644 --- a/iOS/Settings/SettingsLocalAccountView.swift +++ b/iOS/Settings/SettingsLocalAccountView.swift @@ -20,9 +20,9 @@ struct SettingsLocalAccountView : View { SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: Account.defaultLocalAccountName).padding() ) { HStack { - Spacer() - TextField($name, placeholder: Text("Name (Optional)")) - Spacer() + Text("Name") + Divider() + TextField($name, placeholder: Text("(Optional)")) } } Section { diff --git a/iOS/Settings/SettingsSubscriptionsExportDocumentPickerView.swift b/iOS/Settings/SettingsSubscriptionsExportDocumentPickerView.swift new file mode 100644 index 000000000..a471c7d27 --- /dev/null +++ b/iOS/Settings/SettingsSubscriptionsExportDocumentPickerView.swift @@ -0,0 +1,31 @@ +// +// SettingsSubscriptionsExportDocumentPickerView.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 6/16/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account + +struct SettingsSubscriptionsExportDocumentPickerView : UIViewControllerRepresentable { + var account: Account + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIDocumentPickerViewController { + + let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) + let filename = "Subscriptions-\(accountName).opml" + let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(filename) + + let opmlString = OPMLExporter.OPMLString(with: account, title: filename) + try? opmlString.write(to: tempFile, atomically: true, encoding: String.Encoding.utf8) + + return UIDocumentPickerViewController(url: tempFile, in: .exportToService) + } + + func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext) { + // + } + +} diff --git a/iOS/Settings/SettingsSubscriptionsImportDocumentPickerView.swift b/iOS/Settings/SettingsSubscriptionsImportDocumentPickerView.swift new file mode 100644 index 000000000..4f4043a92 --- /dev/null +++ b/iOS/Settings/SettingsSubscriptionsImportDocumentPickerView.swift @@ -0,0 +1,43 @@ +// +// SettingsSubscriptionsImportDocumentPickerView.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 6/16/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account + +struct SettingsSubscriptionsImportDocumentPickerView : UIViewControllerRepresentable { + var account: Account + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIDocumentPickerViewController { + let docPicker = UIDocumentPickerViewController(documentTypes: ["public.xml", "org.opml.opml"], in: .import) + docPicker.delegate = context.coordinator + return docPicker + } + + func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext) { + // + } + + func makeCoordinator() -> Coordinator { + return Coordinator(self) + } + + class Coordinator : NSObject, UIDocumentPickerDelegate { + var parent: SettingsSubscriptionsImportDocumentPickerView + + init(_ view: SettingsSubscriptionsImportDocumentPickerView) { + self.parent = view + } + + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + for url in urls { + parent.account.importOPML(url) { result in} + } + } + + } +} diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index d0ddae653..1ca917abc 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -10,9 +10,15 @@ import SwiftUI 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 + var body: some View { NavigationView { List { @@ -31,30 +37,16 @@ struct SettingsView : View { Section(header: Text("ABOUT")) { Text("About NetNewsWire") - - Button(action: { - UIApplication.shared.open(URL(string: "https://ranchero.com/netnewswire/")!, options: [:]) - }) { - Text("Website") - } - Button(action: { - UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire")!, options: [:]) - }) { - Text("Github Repository") - } - - Button(action: { - UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")!, options: [:]) - }) { - Text("Bug Tracker") - } - - Button(action: { - UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")!, options: [:]) - }) { - Text("Technotes") - } + PresentationButton(Text("Website"), destination: SafariView(url: URL(string: "https://ranchero.com/netnewswire/")!)) + + PresentationButton(Text("Github Repository"), destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire")!)) + + PresentationButton(Text("Bug Tracker"), destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")!)) + + PresentationButton(Text("Technotes"), destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")!)) + + PresentationButton(Text("How to Support NetNewsWire"), destination: SafariView(url: URL(string: "https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown")!)) Text("Add NetNewsWire News Feed") @@ -76,10 +68,23 @@ struct SettingsView : View { Text(interval.description()).tag(interval) } } - Text("Import Subscriptions...") - Text("Export Subscriptions...") + 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) + } .listStyle(.grouped) .navigationBarTitle(Text("Settings"), displayMode: .inline) @@ -87,6 +92,32 @@ struct SettingsView : View { } } + var createSubscriptionsImportAccounts: ActionSheet { + var buttons = [ActionSheet.Button]() + for account in viewModel.accounts { + 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) + } + class ViewModel: BindableObject { let didChange = PassthroughSubject() diff --git a/iOS/Wrappers/SafariView.swift b/iOS/Wrappers/SafariView.swift new file mode 100644 index 000000000..6b5d78334 --- /dev/null +++ b/iOS/Wrappers/SafariView.swift @@ -0,0 +1,52 @@ +// +// SafariView.swift +// NetNewsWire-iOS +// +// Created by Stuart Breckenridge on 16/6/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import SwiftUI +import SafariServices + +struct SafariView : UIViewControllerRepresentable { + + let url: URL + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> SFSafariViewController { + let safari = SFSafariViewController(url: url) + safari.delegate = context.coordinator + return safari + } + + func updateUIViewController(_ uiViewController: SFSafariViewController, context: UIViewControllerRepresentableContext) { + // + } + + func makeCoordinator() -> Coordinator { + return Coordinator(self) + } + + class Coordinator : NSObject, SFSafariViewControllerDelegate { + var parent: SafariView + + init(_ safariView: SafariView) { + self.parent = safariView + } + + // MARK: SFSafariViewControllerDelegate + func safariViewControllerDidFinish(_ controller: SFSafariViewController) { + + } + + func safariViewController(_ controller: SFSafariViewController, initialLoadDidRedirectTo URL: URL) { + + } + + func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) { + + } + } +} + +