NetNewsWire/Multiplatform/iOS/Settings/SettingsView.swift

246 lines
6.9 KiB
Swift

//
// SettingsView.swift
// Multiplatform iOS
//
// Created by Stuart Breckenridge on 30/6/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
import UniformTypeIdentifiers
struct SettingsView: View {
@Environment(\.presentationMode) var presentationMode
@Environment(\.exportFiles) var exportAction
@Environment(\.importFiles) var importAction
@StateObject private var viewModel = SettingsModel()
@StateObject private var feedsSettingsModel = FeedsSettingsModel()
@StateObject private var settings = AppDefaults.shared
var body: some View {
NavigationView {
List {
systemSettings
accounts
importExport
timeline
articles
appearance
help
}
.listStyle(InsetGroupedListStyle())
.navigationBarTitle("Settings", displayMode: .inline)
.navigationBarItems(leading:
HStack {
Button("Done") {
presentationMode.wrappedValue.dismiss()
}
}
)
}
.sheet(isPresented: $viewModel.presentSheet, content: {
SafariView(url: viewModel.selectedWebsite.url!)
})
}
var systemSettings: some View {
Section(header: Text("Notifications, Badge, Data, & More"), content: {
Button(action: {
UIApplication.shared.open(URL(string: "\(UIApplication.openSettingsURLString)")!)
}, label: {
Text("Open System Settings").foregroundColor(.primary)
})
})
}
var accounts: some View {
Section(header: Text("Accounts"), content: {
ForEach(0..<viewModel.accounts.count, id: \.hashValue , content: { i in
NavigationLink(
destination: SettingsDetailAccountView(viewModel.accounts[i]),
label: {
Text(viewModel.accounts[i].nameForDisplay)
})
})
NavigationLink(
destination: SettingsAddAccountView(),
label: {
Text("Add Account")
})
})
}
var importExport: some View {
Section(header: Text("Feeds"), content: {
if viewModel.activeAccounts.count > 1 {
NavigationLink("Import Subscriptions", destination: importOptions)
}
else {
Button(action:{
if feedsSettingsModel.checkForActiveAccount() {
importOPML(account: viewModel.activeAccounts.first)
}
}) {
Text("Import Subscriptions")
.foregroundColor(.primary)
}
}
if viewModel.accounts.count > 1 {
NavigationLink("Export Subscriptions", destination: exportOptions)
}
else {
Button(action:{
exportOPML(account: viewModel.accounts.first)
}) {
Text("Export Subscriptions")
.foregroundColor(.primary)
}
}
})
.alert(isPresented: $feedsSettingsModel.showError) {
Alert(
title: Text(feedsSettingsModel.feedsSettingsError!.title ?? "Oops"),
message: Text(feedsSettingsModel.feedsSettingsError!.localizedDescription),
dismissButton: Alert.Button.cancel({
feedsSettingsModel.feedsSettingsError = FeedsSettingsError.none
}))
}
}
var importOptions: some View {
List {
Section(header: Text("Choose an account to receive the imported feeds and folders"), content: {
ForEach(0..<viewModel.activeAccounts.count, id: \.hashValue , content: { i in
Button {
importOPML(account: viewModel.activeAccounts[i])
} label: {
Text(viewModel.activeAccounts[i].nameForDisplay)
}
})
})
}
.listStyle(InsetGroupedListStyle())
.navigationBarTitle("Import Subscriptions", displayMode: .inline)
}
var exportOptions: some View {
List {
Section(header: Text("Choose an account with the subscriptions to export"), content: {
ForEach(0..<viewModel.accounts.count, id: \.hashValue , content: { i in
Button {
exportOPML(account: viewModel.accounts[i])
} label: {
Text(viewModel.accounts[i].nameForDisplay)
}
})
})
}
.listStyle(InsetGroupedListStyle())
.navigationBarTitle("Export Subscriptions", displayMode: .inline)
}
var timeline: some View {
Section(header: Text("Timeline"), content: {
Toggle("Sort Oldest to Newest", isOn: $settings.timelineSortDirection)
Toggle("Group by Feed", isOn: $settings.timelineGroupByFeed)
Toggle("Refresh to Clear Read Articles", isOn: $settings.refreshClearsReadArticles)
NavigationLink(
destination: TimelineLayoutView().environmentObject(settings),
label: {
Text("Timeline Layout")
})
}).toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
var articles: some View {
Section(header: Text("Articles"), content: {
Toggle("Confirm Mark All as Read", isOn: .constant(true))
Toggle(isOn: .constant(true), label: {
VStack(alignment: .leading, spacing: 4) {
Text("Enable Full Screen Articles")
Text("Tap the article top bar to enter Full Screen. Tap the bottom or top to exit.").font(.caption).foregroundColor(.secondary)
}
})
}).toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
var appearance: some View {
Section(header: Text("Appearance"), content: {
NavigationLink(
destination: ColorPaletteContainerView().environmentObject(settings),
label: {
HStack {
Text("Color Palette")
Spacer()
Text(settings.userInterfaceColorPalette.description)
.foregroundColor(.secondary)
}
})
})
}
var help: some View {
Section(header: Text("Help"), footer: Text(appVersion()).font(.caption2), content: {
Button("NetNewsWire Help", action: {
viewModel.selectedWebsite = .netNewsWireHelp
}).foregroundColor(.primary)
Button("Website", action: {
viewModel.selectedWebsite = .netNewsWire
}).foregroundColor(.primary)
Button("How To Support NetNewsWire", action: {
viewModel.selectedWebsite = .supportNetNewsWire
}).foregroundColor(.primary)
Button("Github Repository", action: {
viewModel.selectedWebsite = .github
}).foregroundColor(.primary)
Button("Bug Tracker", action: {
viewModel.selectedWebsite = .bugTracker
}).foregroundColor(.primary)
Button("NetNewsWire Slack", action: {
viewModel.selectedWebsite = .netNewsWireSlack
}).foregroundColor(.primary)
NavigationLink(
destination: SettingsAboutView(),
label: {
Text("About NetNewsWire")
})
})
}
private func appVersion() -> String {
let dict = NSDictionary(contentsOf: Bundle.main.url(forResource: "Info", withExtension: "plist")!)
let version = dict?.object(forKey: "CFBundleShortVersionString") as? String ?? ""
let build = dict?.object(forKey: "CFBundleVersion") as? String ?? ""
return "NetNewsWire \(version) (Build \(build))"
}
private func exportOPML(account: Account?) {
guard let account = account,
let url = feedsSettingsModel.generateExportURL(for: account) else {
return
}
exportAction(moving: url) { _ in }
}
private func importOPML(account: Account?) {
let types = [UTType(filenameExtension: "opml"), UTType("public.xml")].compactMap { $0 }
importAction(multipleOfType: types) { (result: Result<[URL], Error>?) in
if let urls = try? result?.get() {
feedsSettingsModel.processImportedFiles(urls, account)
}
}
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
}
}