More Options Added to Settings

This commit is contained in:
Stuart Breckenridge
2022-11-29 19:12:44 +08:00
parent 08d46f877b
commit 36fa87b8c4
12 changed files with 346 additions and 105 deletions

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "app.opml.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -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 {

View File

@@ -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"))

View File

@@ -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()
}
}

View File

@@ -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"
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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")
}
}
}
}

View 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?
}