From 36fa87b8c46bc4a0e2747302e5f0bfe0e3c05760 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 29 Nov 2022 19:12:44 +0800 Subject: [PATCH] More Options Added to Settings --- NetNewsWire.xcodeproj/project.pbxproj | 36 +++++-- Shared/Importers/OPMLDocument.swift | 42 ++++++++ .../Settings/app.opml.imageset/Contents.json | 12 +++ .../Settings/app.opml.imageset/app.opml.pdf | Bin 0 -> 5245 bytes .../AccountsManagementView.swift | 49 ++++++---- .../ColorPaletteSelectorView.swift | 0 .../DisplayAndBehaviorsView.swift | 1 + .../NewArticleNotificationsView.swift | 60 ++++++++++++ .../Settings View/SettingsHelpSheets.swift | 47 ++------- iOS/Settings/Settings View/SettingsRows.swift | 81 +++++++++++----- iOS/Settings/Settings View/SettingsView.swift | 90 +++++++++++++++--- .../Settings View/SettingsViewModel.swift | 33 +++++++ 12 files changed, 346 insertions(+), 105 deletions(-) create mode 100644 Shared/Importers/OPMLDocument.swift create mode 100644 iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/Contents.json create mode 100644 iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/app.opml.pdf rename iOS/Settings/{Account Management View => Account Management Views}/AccountsManagementView.swift (74%) rename iOS/Settings/{Appearance View => Appearance Views}/ColorPaletteSelectorView.swift (100%) rename iOS/Settings/{Appearance View => Appearance Views}/DisplayAndBehaviorsView.swift (95%) create mode 100644 iOS/Settings/New Article Notifications Views/NewArticleNotificationsView.swift create mode 100644 iOS/Settings/Settings View/SettingsViewModel.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 6053df0c3..f729266ac 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -849,6 +849,11 @@ DDF9E1D728EDF2FC000BC355 /* notificationSoundBlip.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = DDF9E1D628EDF2FC000BC355 /* notificationSoundBlip.mp3 */; }; DDF9E1D828EDF2FC000BC355 /* notificationSoundBlip.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = DDF9E1D628EDF2FC000BC355 /* notificationSoundBlip.mp3 */; }; DDF9E1D928EDF2FC000BC355 /* notificationSoundBlip.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = DDF9E1D628EDF2FC000BC355 /* notificationSoundBlip.mp3 */; }; + DF3630EB2936183D00326FB8 /* OPMLDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF3630EA2936183D00326FB8 /* OPMLDocument.swift */; }; + DF3630EC2936183D00326FB8 /* OPMLDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF3630EA2936183D00326FB8 /* OPMLDocument.swift */; }; + DF3630ED2936183D00326FB8 /* OPMLDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF3630EA2936183D00326FB8 /* OPMLDocument.swift */; }; + DF3630EF293618A900326FB8 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF3630EE293618A900326FB8 /* SettingsViewModel.swift */; }; + DF394F0029357A180081EB6E /* NewArticleNotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF394EFF29357A180081EB6E /* NewArticleNotificationsView.swift */; }; DF59F072292085B800ACD33D /* ColorPaletteSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF59F071292085B800ACD33D /* ColorPaletteSelectorView.swift */; }; DF59F0742920DB5100ACD33D /* AccountsManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */; }; DF5AD10128D6922200CA3BF7 /* SmartFeedSummaryWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1768144D2564BCE000D98635 /* SmartFeedSummaryWidget.swift */; }; @@ -1602,6 +1607,9 @@ D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Folder+Scriptability.swift"; sourceTree = ""; }; DD82AB09231003F6002269DF /* SharingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharingTests.swift; sourceTree = ""; }; DDF9E1D628EDF2FC000BC355 /* notificationSoundBlip.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = notificationSoundBlip.mp3; sourceTree = ""; }; + DF3630EA2936183D00326FB8 /* OPMLDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLDocument.swift; sourceTree = ""; }; + DF3630EE293618A900326FB8 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; + DF394EFF29357A180081EB6E /* NewArticleNotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewArticleNotificationsView.swift; sourceTree = ""; }; DF59F071292085B800ACD33D /* ColorPaletteSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPaletteSelectorView.swift; sourceTree = ""; }; DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsManagementView.swift; sourceTree = ""; }; DF790D6128E990A900455FC7 /* AboutData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutData.swift; sourceTree = ""; }; @@ -2015,8 +2023,9 @@ 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */, 51A16990235E10D600EB091F /* Settings.storyboard */, DFD406F8291FB5D500C02962 /* Settings View */, - DFD406FD291FDBD900C02962 /* Appearance View */, - DF59F0752920E42000ACD33D /* Account Management View */, + DFD406FD291FDBD900C02962 /* Appearance Views */, + DF59F0752920E42000ACD33D /* Account Management Views */, + DF3630E92936038400326FB8 /* New Article Notifications Views */, 516A09382360A2AE00EAE89B /* SettingsComboTableViewCell.swift */, 516A091D23609A3600EAE89B /* SettingsComboTableViewCell.xib */, 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */, @@ -2775,6 +2784,7 @@ children = ( 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */, 84A3EE52223B667F00557320 /* DefaultFeeds.opml */, + DF3630EA2936183D00326FB8 /* OPMLDocument.swift */, ); path = Importers; sourceTree = ""; @@ -2890,12 +2900,20 @@ path = Scriptability; sourceTree = ""; }; - DF59F0752920E42000ACD33D /* Account Management View */ = { + DF3630E92936038400326FB8 /* New Article Notifications Views */ = { + isa = PBXGroup; + children = ( + DF394EFF29357A180081EB6E /* NewArticleNotificationsView.swift */, + ); + path = "New Article Notifications Views"; + sourceTree = ""; + }; + DF59F0752920E42000ACD33D /* Account Management Views */ = { isa = PBXGroup; children = ( DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */, ); - path = "Account Management View"; + path = "Account Management Views"; sourceTree = ""; }; DFC14F0928EA51AB00F6EE86 /* About */ = { @@ -2912,19 +2930,20 @@ isa = PBXGroup; children = ( DFD406F4291F79C900C02962 /* SettingsView.swift */, + DF3630EE293618A900326FB8 /* SettingsViewModel.swift */, DFD406F9291FB5E400C02962 /* SettingsRows.swift */, DFD406FB291FB63B00C02962 /* SettingsHelpSheets.swift */, ); path = "Settings View"; sourceTree = ""; }; - DFD406FD291FDBD900C02962 /* Appearance View */ = { + DFD406FD291FDBD900C02962 /* Appearance Views */ = { isa = PBXGroup; children = ( DFD406FE291FDC0C00C02962 /* DisplayAndBehaviorsView.swift */, DF59F071292085B800ACD33D /* ColorPaletteSelectorView.swift */, ); - path = "Appearance View"; + path = "Appearance Views"; sourceTree = ""; }; /* End PBXGroup section */ @@ -3892,6 +3911,7 @@ 65ED3FBD235DEF6C0081F399 /* AppDefaults.swift in Sources */, 65ED3FBE235DEF6C0081F399 /* Account+Scriptability.swift in Sources */, 65ED3FBF235DEF6C0081F399 /* NothingInspectorViewController.swift in Sources */, + DF3630EC2936183D00326FB8 /* OPMLDocument.swift in Sources */, 1710B92A255246F900679C0D /* EnableExtensionPointHelpView.swift in Sources */, 51927A0528E28D1C000AE856 /* MainWindow.swift in Sources */, 65ED3FC0235DEF6C0081F399 /* AppNotifications.swift in Sources */, @@ -4110,6 +4130,7 @@ 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, 51AB8AB323B7F4C6008F147D /* WebViewController.swift in Sources */, DFD406F7291FB1A600C02962 /* SafariView.swift in Sources */, + DF3630ED2936183D00326FB8 /* OPMLDocument.swift in Sources */, 516A09392360A2AE00EAE89B /* SettingsComboTableViewCell.swift in Sources */, 176813D22564BA5900D98635 /* WidgetDataEncoder.swift in Sources */, 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */, @@ -4140,6 +4161,7 @@ DF790D6228E990A900455FC7 /* AboutData.swift in Sources */, 177A0C2D25454AAB00D7EAF6 /* ReaderAPIAccountViewController.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, + DF394F0029357A180081EB6E /* NewArticleNotificationsView.swift in Sources */, 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */, 5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */, 51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */, @@ -4212,6 +4234,7 @@ 51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */, 5195C1DC2720BD3000888867 /* MasterFeedRowIdentifier.swift in Sources */, 5108F6D823763094001ABC45 /* TickMarkSlider.swift in Sources */, + DF3630EF293618A900326FB8 /* SettingsViewModel.swift in Sources */, 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */, 51B5C8C023F3866C00032075 /* ExtensionFeedAddRequestFile.swift in Sources */, 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */, @@ -4414,6 +4437,7 @@ 51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */, 84B99C9D1FAE83C600ECDEDB /* DeleteCommand.swift in Sources */, 849A97541ED9EAC0007D329B /* AddWebFeedWindowController.swift in Sources */, + DF3630EB2936183D00326FB8 /* OPMLDocument.swift in Sources */, 5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */, 178A9F9D2549449F00AB7E9D /* AddAccountsView.swift in Sources */, 51C4CFF024D37D1F00AF9874 /* Secrets.swift in Sources */, diff --git a/Shared/Importers/OPMLDocument.swift b/Shared/Importers/OPMLDocument.swift new file mode 100644 index 000000000..a304d9fa5 --- /dev/null +++ b/Shared/Importers/OPMLDocument.swift @@ -0,0 +1,42 @@ +// +// OPMLDocument.swift +// NetNewsWire +// +// Created by Stuart Breckenridge on 29/11/2022. +// Copyright © 2022 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account +import UniformTypeIdentifiers + +public struct OPMLDocument: FileDocument { + + public var account: Account! + + public static var readableContentTypes: [UTType] { + UTType.types(tag: "opml", tagClass: .filenameExtension, conformingTo: nil) + } + + public static var writableContentTypes: [UTType] { + UTType.types(tag: "opml", tagClass: .filenameExtension, conformingTo: nil) + } + + public init(configuration: ReadConfiguration) throws { + + } + + public init(_ account: Account) throws { + self.account = account + } + + public func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { + 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) + let wrapper = try FileWrapper(url: tempFile) + return wrapper + } +} diff --git a/iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/Contents.json b/iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/Contents.json new file mode 100644 index 000000000..bcca90bba --- /dev/null +++ b/iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "app.opml.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/app.opml.pdf b/iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/app.opml.pdf new file mode 100644 index 0000000000000000000000000000000000000000..edcb0a6b9e6e46d1b7b1d0fb226ea4995d22af5f GIT binary patch literal 5245 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 literal 0 HcmV?d00001 diff --git a/iOS/Settings/Account Management View/AccountsManagementView.swift b/iOS/Settings/Account Management Views/AccountsManagementView.swift similarity index 74% rename from iOS/Settings/Account Management View/AccountsManagementView.swift rename to iOS/Settings/Account Management Views/AccountsManagementView.swift index ea19c7d0a..f46d12f49 100644 --- a/iOS/Settings/Account Management View/AccountsManagementView.swift +++ b/iOS/Settings/Account Management Views/AccountsManagementView.swift @@ -14,22 +14,15 @@ struct AccountsManagementView: View { @State private var showAddAccountSheet: Bool = false var cancellables = Set() - @State private var updated: Bool = false - + @State private var sortedAccounts = [Account]() var body: some View { List { - ForEach(AccountManager.shared.sortedActiveAccounts, id: \.accountID) { account in - Section(footer: accountFooterText(account)) { + ForEach(sortedAccounts, id: \.self) { account in + Section(header: Text("")) { accountRow(account) } } - - Section(header: Text("Inactive Accounts"), footer: inactiveFooterText) { - ForEach(0.. some View { - Group { + VStack(alignment: .leading) { HStack { Image(uiImage: account.smallIcon!.image) .resizable() @@ -77,14 +74,32 @@ struct AccountsManagementView: View { TextField(text: Binding(get: { account.nameForDisplay }, set: { account.name = $0 })) { Text(account.nameForDisplay) }.foregroundColor(.secondary) + Spacer() + Toggle(isOn: Binding( + get: { account.isActive }, + set: { account.isActive = $0 } + )) { + Text("") + } } - Toggle(isOn: Binding( - get: { account.isActive }, - set: { account.isActive = $0 } - )) { - Text("Active") + if account.type != .onMyMac { + Divider() + .edgesIgnoringSafeArea(.all) + HStack { + Spacer() + Button { + // Remove account + } label: { + Text("Remove Account") + .foregroundColor(.red) + .bold() + } + + Spacer() + } } } + } var inactiveFooterText: some View { diff --git a/iOS/Settings/Appearance View/ColorPaletteSelectorView.swift b/iOS/Settings/Appearance Views/ColorPaletteSelectorView.swift similarity index 100% rename from iOS/Settings/Appearance View/ColorPaletteSelectorView.swift rename to iOS/Settings/Appearance Views/ColorPaletteSelectorView.swift diff --git a/iOS/Settings/Appearance View/DisplayAndBehaviorsView.swift b/iOS/Settings/Appearance Views/DisplayAndBehaviorsView.swift similarity index 95% rename from iOS/Settings/Appearance View/DisplayAndBehaviorsView.swift rename to iOS/Settings/Appearance Views/DisplayAndBehaviorsView.swift index 53d65716b..365db386a 100644 --- a/iOS/Settings/Appearance View/DisplayAndBehaviorsView.swift +++ b/iOS/Settings/Appearance Views/DisplayAndBehaviorsView.swift @@ -32,6 +32,7 @@ struct DisplayAndBehaviorsView: View { get: { !appDefaults.useSystemBrowser }, set: { appDefaults.useSystemBrowser = !$0 } )) + // TODO: Add Reader Mode Defaults here. See #3684. } } .navigationTitle(Text("Display & Behaviors")) diff --git a/iOS/Settings/New Article Notifications Views/NewArticleNotificationsView.swift b/iOS/Settings/New Article Notifications Views/NewArticleNotificationsView.swift new file mode 100644 index 000000000..13b68b9f8 --- /dev/null +++ b/iOS/Settings/New Article Notifications Views/NewArticleNotificationsView.swift @@ -0,0 +1,60 @@ +// +// NewArticleNotificationsView.swift +// NetNewsWire-iOS +// +// Created by Stuart Breckenridge on 29/11/2022. +// Copyright © 2022 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account +import RSCore + +struct NewArticleNotificationsView: View { + + @State private var activeAccounts = AccountManager.shared.sortedActiveAccounts + + var body: some View { + List { + ForEach(activeAccounts, id: \.accountID) { account in + Section(header: Text(account.nameForDisplay)) { + ForEach(sortedWebFeedsForAccount(account), id: \.webFeedID) { feed in + notificationToggle(feed) + } + } + } + .navigationTitle(Text("New Article Notifications")) + .navigationBarTitleDisplayMode(.inline) + .onReceive(NotificationCenter.default.publisher(for: .WebFeedIconDidBecomeAvailable)) { _ in + activeAccounts = AccountManager.shared.sortedActiveAccounts + } + } + } + + + private func sortedWebFeedsForAccount(_ account: Account) -> [WebFeed] { + return Array(account.flattenedWebFeeds()).sorted(by: { $0.nameForDisplay.caseInsensitiveCompare($1.nameForDisplay) == .orderedAscending }) + } + + private func notificationToggle(_ webfeed: WebFeed) -> some View { + HStack { + Image(uiImage: IconImageCache.shared.imageFor(webfeed.feedID!)!.image) + .resizable() + .frame(width: 25, height: 25) + .cornerRadius(4) + + Text(webfeed.nameForDisplay) + Spacer() + Toggle("", isOn: Binding( + get: { webfeed.isNotifyAboutNewArticles ?? false }, + set: { webfeed.isNotifyAboutNewArticles = $0 })) + } + + } +} + +struct NewArticleNotificationsView_Previews: PreviewProvider { + static var previews: some View { + NewArticleNotificationsView() + } +} diff --git a/iOS/Settings/Settings View/SettingsHelpSheets.swift b/iOS/Settings/Settings View/SettingsHelpSheets.swift index 8fbda4166..726961149 100644 --- a/iOS/Settings/Settings View/SettingsHelpSheets.swift +++ b/iOS/Settings/Settings View/SettingsHelpSheets.swift @@ -8,70 +8,35 @@ import Foundation -enum HelpSheet: CustomStringConvertible, CaseIterable { + +public enum HelpSheet: CustomStringConvertible, CaseIterable { - case help, website, releaseNotes, howToSupport, gitHubRepository, bugTracker, technotes, slack + case help, website - var description: String { + public var description: String { switch self { case .help: return NSLocalizedString("NetNewsWire Help", comment: "NetNewsWire Help") case .website: return NSLocalizedString("Website", comment: "Website") - case .releaseNotes: - return NSLocalizedString("Release Notes", comment: "Release Notes") - case .howToSupport: - return NSLocalizedString("How to Support NetNewsWire", comment: "How to Support") - case .gitHubRepository: - return NSLocalizedString("GitHub Respository", comment: "Github") - case .bugTracker: - return NSLocalizedString("Bug Tracker", comment: "Bug Tracker") - case .technotes: - return NSLocalizedString("Technotes", comment: "Technotes") - case .slack: - return NSLocalizedString("Slack", comment: "Slack") } } - var url: URL { + public var url: URL { switch self { case .help: return URL(string: "https://netnewswire.com/help/ios/6.1/en/")! case .website: return URL(string: "https://netnewswire.com/")! - case .releaseNotes: - return URL(string: URL.releaseNotes.absoluteString)! - case .howToSupport: - return URL(string: "https://github.com/brentsimmons/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown")! - case .gitHubRepository: - return URL(string: "https://github.com/brentsimmons/NetNewsWire")! - case .bugTracker: - return URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")! - case .technotes: - return URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/main/Technotes")! - case .slack: - return URL(string: "https://netnewswire.com/slack")! } } - var systemImage: String { + public var systemImage: String { switch self { case .help: return "questionmark.app" case .website: return "globe" - case .releaseNotes: - return "quote.opening" - case .howToSupport: - return "person.3.fill" - case .gitHubRepository: - return "archivebox" - case .bugTracker: - return "ladybug" - case .technotes: - return "chevron.left.slash.chevron.right" - case .slack: - return "quote.bubble.fill" } } } diff --git a/iOS/Settings/Settings View/SettingsRows.swift b/iOS/Settings/Settings View/SettingsRows.swift index b26597be5..bbf2a8a11 100644 --- a/iOS/Settings/Settings View/SettingsRows.swift +++ b/iOS/Settings/Settings View/SettingsRows.swift @@ -8,6 +8,10 @@ import SwiftUI import Account +import UniformTypeIdentifiers + + + // MARK: - Headers @@ -57,9 +61,9 @@ struct SettingsViewRows { /// This row, when tapped, will push the New Article Notifications /// screen in to view. static var ConfigureNewArticleNotifications: some View { - NavigationLink(destination: NotificationsViewControllerRepresentable().edgesIgnoringSafeArea(.all)) { + NavigationLink(destination: NewArticleNotificationsView()) { Label { - Text("Notifications and Sounds") + Text("New Article Notifications") } icon: { Image("notifications.sounds") .resizable() @@ -115,29 +119,47 @@ struct SettingsViewRows { } } - /// This row, when tapped, will push the the Import subscriptions screen - /// in to view. - static var ImportSubscription: some View { - Label { - Text("Import Subscriptions") - } icon: { - Image(systemName: "square.and.arrow.down") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 25.0, height: 25.0) - } - } - - /// This row, when tapped, will push the the Export subscriptions screen - /// in to view. - static var ExportSubscription: some View { - Label { - Text("Export Subscriptions") - } icon: { - Image(systemName: "square.and.arrow.up") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 25.0, height: 25.0) + /// This row, when tapped, will present an Import/Export + /// menu. + static func ImportExportOPML(showImportView: Binding, showExportView: Binding, importAccount: Binding, exportDocument: Binding) -> some View { + Menu { + Menu { + ForEach(AccountManager.shared.sortedActiveAccounts, id: \.self) { account in + Button(account.nameForDisplay) { + importAccount.wrappedValue = account + showImportView.wrappedValue = true + } + } + } label: { + Label("Import Subscriptions To...", systemImage: "arrow.down.doc") + } + Divider() + Menu { + ForEach(AccountManager.shared.sortedAccounts, id: \.self) { account in + Button(account.nameForDisplay) { + do { + let document = try OPMLDocument(account) + exportDocument.wrappedValue = document + showExportView.wrappedValue = true + } catch { + print(error.localizedDescription) + } + } + } + } label: { + Label("Export Subscriptions From...", systemImage: "arrow.up.doc") + } + } label: { + Label { + Text("Import/Export Subscriptions") + .foregroundColor(.primary) + + } icon: { + Image("app.opml") + .resizable() + .frame(width: 25.0, height: 25.0) + .clipShape(RoundedRectangle(cornerRadius: 6)) + } } } @@ -197,6 +219,8 @@ struct SettingsViewRows { Toggle("Open Links in NetNewsWire", isOn: preference) } + // TODO: Add Reader Mode Defaults here. See #3684. + static func EnableFullScreenArticles(_ preference: Binding) -> some View { Toggle(isOn: preference) { VStack(alignment: .leading, spacing: 4) { @@ -253,7 +277,12 @@ struct SettingsViewRows { Label { Text("About NetNewsWire") } icon: { - Image(systemName: "questionmark.square.dashed") + Image(systemName: "info.circle") + .resizable() + .renderingMode(.template) + .foregroundColor(Color(uiColor: .tertiaryLabel)) + .aspectRatio(contentMode: .fit) + .frame(width: 25.0, height: 25.0) } } } diff --git a/iOS/Settings/Settings View/SettingsView.swift b/iOS/Settings/Settings View/SettingsView.swift index 97e1a0c4a..b1639a85c 100644 --- a/iOS/Settings/Settings View/SettingsView.swift +++ b/iOS/Settings/Settings View/SettingsView.swift @@ -8,37 +8,42 @@ import SwiftUI import Account +import UniformTypeIdentifiers +import UserNotifications struct SettingsView: View { @StateObject private var appDefaults = AppDefaults.shared - @State private var showAddAccountView: Bool = false - @State private var helpSheet: HelpSheet = .help - @State private var showHelpSheet: Bool = false - @State private var showAbout: Bool = false + @StateObject private var viewModel = SettingsViewModel() var body: some View { NavigationView { List { - // System Settings - Section(footer: Text("Configure NetNewsWire's access to Siri, background app refresh, mobile data, and more.")) { + // Device Permissions + Section(header: Text("Device Permissions"), footer: Text("Configure NetNewsWire's access to Siri, background app refresh, mobile data, and more.")) { SettingsViewRows.OpenSystemSettings } - Section(footer: Text("Add, delete, enable or disable accounts and extensions.")) { + // Account/Extensions/OPML Management + Section(header: Text("Accounts & Extensions"), footer: Text("Add, delete, enable, or disable accounts and extensions.")) { SettingsViewRows.AddAccount SettingsViewRows.AddExtension + SettingsViewRows.ImportExportOPML(showImportView: $viewModel.showImportView, showExportView: $viewModel.showExportView, importAccount: $viewModel.importAccount, exportDocument: $viewModel.exportDocument) } - Section(footer: Text("Configure the look, feel, and behavior of NetNewsWire.")) { + // Appearance + Section(header: Text("Appearance"), footer: Text("Manage the look, feel, and behavior of NetNewsWire.")) { SettingsViewRows.ConfigureAppearance - SettingsViewRows.ConfigureNewArticleNotifications + if viewModel.notificationPermissions == .authorized { + SettingsViewRows.ConfigureNewArticleNotifications + } } - + + // Help Section { ForEach(0..