mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
More Options Added to Settings
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
DD82AB09231003F6002269DF /* SharingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharingTests.swift; sourceTree = "<group>"; };
|
||||
DDF9E1D628EDF2FC000BC355 /* notificationSoundBlip.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = notificationSoundBlip.mp3; sourceTree = "<group>"; };
|
||||
DF3630EA2936183D00326FB8 /* OPMLDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLDocument.swift; sourceTree = "<group>"; };
|
||||
DF3630EE293618A900326FB8 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
DF394EFF29357A180081EB6E /* NewArticleNotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewArticleNotificationsView.swift; sourceTree = "<group>"; };
|
||||
DF59F071292085B800ACD33D /* ColorPaletteSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPaletteSelectorView.swift; sourceTree = "<group>"; };
|
||||
DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsManagementView.swift; sourceTree = "<group>"; };
|
||||
DF790D6128E990A900455FC7 /* AboutData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutData.swift; sourceTree = "<group>"; };
|
||||
@@ -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 = "<group>";
|
||||
@@ -2890,12 +2900,20 @@
|
||||
path = Scriptability;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DF59F0752920E42000ACD33D /* Account Management View */ = {
|
||||
DF3630E92936038400326FB8 /* New Article Notifications Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DF394EFF29357A180081EB6E /* NewArticleNotificationsView.swift */,
|
||||
);
|
||||
path = "New Article Notifications Views";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DF59F0752920E42000ACD33D /* Account Management Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */,
|
||||
);
|
||||
path = "Account Management View";
|
||||
path = "Account Management Views";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
DFD406FD291FDBD900C02962 /* Appearance View */ = {
|
||||
DFD406FD291FDBD900C02962 /* Appearance Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DFD406FE291FDC0C00C02962 /* DisplayAndBehaviorsView.swift */,
|
||||
DF59F071292085B800ACD33D /* ColorPaletteSelectorView.swift */,
|
||||
);
|
||||
path = "Appearance View";
|
||||
path = "Appearance Views";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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 */,
|
||||
|
||||
42
Shared/Importers/OPMLDocument.swift
Normal file
42
Shared/Importers/OPMLDocument.swift
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
12
iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/Contents.json
vendored
Normal file
12
iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "app.opml.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/app.opml.pdf
vendored
Normal file
BIN
iOS/Resources/Assets.xcassets/Settings/app.opml.imageset/app.opml.pdf
vendored
Normal file
Binary file not shown.
@@ -14,22 +14,15 @@ struct AccountsManagementView: View {
|
||||
|
||||
@State private var showAddAccountSheet: Bool = false
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
@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..<AccountManager.shared.sortedAccounts.filter({ $0.isActive == false }).count, id: \.self) { i in
|
||||
accountRow(AccountManager.shared.sortedAccounts.filter({ $0.isActive == false })[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(Text("Accounts"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
@@ -44,8 +37,12 @@ struct AccountsManagementView: View {
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .AccountStateDidChange)) { _ in
|
||||
updated.toggle()
|
||||
sortedAccounts = AccountManager.shared.sortedAccounts
|
||||
}
|
||||
.onAppear {
|
||||
sortedAccounts = AccountManager.shared.sortedAccounts
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var addAccountButton: some View {
|
||||
@@ -69,7 +66,7 @@ struct AccountsManagementView: View {
|
||||
}
|
||||
|
||||
func accountRow(_ account: Account) -> 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<Bool>(
|
||||
get: { account.isActive },
|
||||
set: { account.isActive = $0 }
|
||||
)) {
|
||||
Text("")
|
||||
}
|
||||
}
|
||||
Toggle(isOn: Binding<Bool>(
|
||||
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 {
|
||||
@@ -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"))
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Bool>, showExportView: Binding<Bool>, importAccount: Binding<Account?>, exportDocument: Binding<OPMLDocument?>) -> 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<Bool>) -> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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..<HelpSheet.allCases.count, id: \.self) { i in
|
||||
SettingsViewRows.ShowHelpSheet(sheet: HelpSheet.allCases[i], selectedSheet: $helpSheet, $showHelpSheet)
|
||||
SettingsViewRows.ShowHelpSheet(sheet: HelpSheet.allCases[i], selectedSheet: $viewModel.helpSheet, $viewModel.showHelpSheet)
|
||||
}
|
||||
SettingsViewRows.AboutNetNewsWire
|
||||
}
|
||||
@@ -47,15 +52,70 @@ struct SettingsView: View {
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle(Text("Settings"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.sheet(isPresented: $showAddAccountView) {
|
||||
.sheet(isPresented: $viewModel.showAddAccountView) {
|
||||
AddAccountViewControllerRepresentable().edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
.sheet(isPresented: $showHelpSheet) {
|
||||
SafariView(url: helpSheet.url)
|
||||
.sheet(isPresented: $viewModel.showHelpSheet) {
|
||||
SafariView(url: viewModel.helpSheet.url)
|
||||
}
|
||||
.sheet(isPresented: $showAbout) {
|
||||
.sheet(isPresented: $viewModel.showAbout) {
|
||||
AboutView()
|
||||
}
|
||||
.task {
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
DispatchQueue.main.async {
|
||||
self.viewModel.notificationPermissions = settings.authorizationStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIScene.willEnterForegroundNotification)) { _ in
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
DispatchQueue.main.async {
|
||||
self.viewModel.notificationPermissions = settings.authorizationStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
.fileImporter(isPresented: $viewModel.showImportView, allowedContentTypes: OPMLDocument.readableContentTypes) { result in
|
||||
switch result {
|
||||
case .success(let url):
|
||||
viewModel.importAccount!.importOPML(url) { importResult in
|
||||
switch importResult {
|
||||
case .success(_):
|
||||
viewModel.showImportSuccess = true
|
||||
case .failure(let error):
|
||||
viewModel.importExportError = error
|
||||
viewModel.showImportExportError = true
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
viewModel.importExportError = error
|
||||
viewModel.showImportExportError = true
|
||||
}
|
||||
}
|
||||
.fileExporter(isPresented: $viewModel.showExportView, document: viewModel.exportDocument, contentType: OPMLDocument.writableContentTypes.first!, onCompletion: { result in
|
||||
switch result {
|
||||
case .success(_):
|
||||
viewModel.showExportSuccess = true
|
||||
case .failure(let error):
|
||||
viewModel.importExportError = error
|
||||
viewModel.showImportExportError = true
|
||||
}
|
||||
})
|
||||
.alert("Imported Successfully", isPresented: $viewModel.showImportSuccess) {
|
||||
Button("Dismiss") {}
|
||||
} message: {
|
||||
Text("Import to your \(viewModel.importAccount?.nameForDisplay ?? "") account has completed.")
|
||||
}
|
||||
.alert("Exported Successfully", isPresented: $viewModel.showExportSuccess) {
|
||||
Button("Dismiss") {}
|
||||
} message: {
|
||||
Text("Your OPML file has been successfully exported.")
|
||||
}
|
||||
.alert("Error", isPresented: $viewModel.showImportExportError) {
|
||||
Button("Dismiss") {}
|
||||
} message: {
|
||||
Text(viewModel.importExportError?.localizedDescription ?? "Import/Export Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
iOS/Settings/Settings View/SettingsViewModel.swift
Normal file
33
iOS/Settings/Settings View/SettingsViewModel.swift
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// SettingsViewModel.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 29/11/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
import UniformTypeIdentifiers
|
||||
import UserNotifications
|
||||
|
||||
|
||||
public final class SettingsViewModel: ObservableObject {
|
||||
|
||||
@Published public var showAddAccountView: Bool = false
|
||||
@Published public var helpSheet: HelpSheet = .help
|
||||
@Published public var showHelpSheet: Bool = false
|
||||
@Published public var showAbout: Bool = false
|
||||
@Published public var notificationPermissions: UNAuthorizationStatus = .notDetermined
|
||||
@Published public var importAccount: Account? = nil
|
||||
@Published public var exportAccount: Account? = nil
|
||||
@Published public var showImportView: Bool = false
|
||||
@Published public var showExportView: Bool = false
|
||||
@Published public var showImportExportError: Bool = false
|
||||
@Published public var importExportError: Error?
|
||||
@Published public var showImportSuccess: Bool = false
|
||||
@Published public var showExportSuccess: Bool = false
|
||||
@Published public var exportDocument: OPMLDocument?
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user