Adds Toolbar for Sidebar & SettingsViews for iOS

This commit is contained in:
Stuart Breckenridge
2020-06-30 21:22:23 +08:00
parent f222dbd0fa
commit 2342ef2b76
7 changed files with 515 additions and 13 deletions

View File

@@ -12,6 +12,7 @@ struct CompactSidebarContainerView: View {
@EnvironmentObject private var sceneModel: SceneModel
@StateObject private var sidebarModel = SidebarModel()
@State private var showSettings: Bool = false
var body: some View {
SidebarView()
@@ -22,8 +23,42 @@ struct CompactSidebarContainerView: View {
sceneModel.sidebarModel = sidebarModel
sidebarModel.delegate = sceneModel
sidebarModel.rebuildSidebarItems()
}.overlay(Group {
#if os(iOS)
SidebarToolbar()
#endif
},alignment: .bottom)
}
var compactToolBar: some View {
VStack {
Divider()
HStack(alignment: .center) {
Button(action: {
showSettings = true
}, label: {
Image(systemName: "gear")
.font(.title3)
.foregroundColor(.accentColor)
}).help("Settings")
Spacer()
Text("Last updated")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
Button(action: {}, label: {
Image(systemName: "plus")
.font(.title3)
.foregroundColor(.accentColor)
}).help("Add")
}
.padding(.horizontal, 16)
.padding(.bottom, 12)
.padding(.top, 4)
}
.background(VisualEffectBlur(blurStyle: .systemChromeMaterial).edgesIgnoringSafeArea(.bottom))
}

View File

@@ -13,7 +13,9 @@ struct RegularSidebarContainerView: View {
@EnvironmentObject private var sceneModel: SceneModel
@StateObject private var sidebarModel = SidebarModel()
var body: some View {
@State private var showSettings: Bool = false
@ViewBuilder var body: some View {
SidebarView()
.environmentObject(sidebarModel)
.navigationTitle(Text("Feeds"))
@@ -23,7 +25,14 @@ struct RegularSidebarContainerView: View {
sidebarModel.delegate = sceneModel
sidebarModel.rebuildSidebarItems()
}
.overlay(Group {
#if os(iOS)
SidebarToolbar()
#endif
},alignment: .bottom)
}
}
struct RegularSidebarContainerView_Previews: PreviewProvider {

View File

@@ -0,0 +1,53 @@
//
// SidebarToolbar.swift
// Multiplatform iOS
//
// Created by Stuart Breckenridge on 30/6/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import SwiftUI
struct SidebarToolbar: View {
@State private var showSettings: Bool = false
var body: some View {
VStack {
Divider()
HStack(alignment: .center) {
Button(action: {
showSettings = true
}, label: {
Image(systemName: "gear")
.font(.title3)
.foregroundColor(.accentColor)
}).help("Settings")
Spacer()
Text("Last updated")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
Button(action: {}, label: {
Image(systemName: "plus")
.font(.title3)
.foregroundColor(.accentColor)
}).help("Add")
}
.padding(.horizontal, 16)
.padding(.bottom, 12)
.padding(.top, 4)
}
.background(VisualEffectBlur(blurStyle: .systemChromeMaterial).edgesIgnoringSafeArea(.bottom))
.sheet(isPresented: $showSettings, onDismiss: { showSettings = false }) {
SettingsView()
}
}
}
struct SidebarToolbar_Previews: PreviewProvider {
static var previews: some View {
SidebarToolbar()
}
}

View File

@@ -0,0 +1,65 @@
//
// SafariView.swift
// Multiplatform iOS
//
// Created by Stuart Breckenridge on 30/6/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import SwiftUI
import SafariServices
private final class Safari: UIViewControllerRepresentable {
typealias UIViewControllerType = SFSafariViewController
var urlToLoad: URL
init(url: URL) {
self.urlToLoad = url
}
func makeUIViewController(context: Context) -> SFSafariViewController {
let viewController = SFSafariViewController(url: urlToLoad)
viewController.delegate = context.coordinator
return viewController
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, SFSafariViewControllerDelegate {
var parent: Safari
init(_ parent: Safari) {
self.parent = parent
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
}
}
}
struct SafariView: View {
var url: URL
var body: some View {
Safari(url: url)
}
}
struct SafariView_Previews: PreviewProvider {
static var previews: some View {
SafariView(url: URL(string: "https://ranchero.com/netnewswire/")!)
}
}

View File

@@ -0,0 +1,204 @@
//
// SettingsView.swift
// Multiplatform iOS
//
// Created by Stuart Breckenridge on 30/6/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
class SettingsViewModel: ObservableObject {
enum HelpSites {
case netNewsWireHelp, netNewsWire, supportNetNewsWire, github, bugTracker, technotes, netNewsWireSlack, none
var url: URL? {
switch self {
case .netNewsWireHelp:
return URL(string: "https://ranchero.com/netnewswire/help/ios/5.0/en/")!
case .netNewsWire:
return URL(string: "https://ranchero.com/netnewswire/")!
case .supportNetNewsWire:
return URL(string: "https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown")!
case .github:
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/master/Technotes")!
case .netNewsWireSlack:
return URL(string: "https://ranchero.com/netnewswire/slack")!
case .none:
return nil
}
}
}
@Published var presentSheet: Bool = false
var selectedWebsite: HelpSites = .none {
didSet {
if selectedWebsite == .none {
presentSheet = false
} else {
presentSheet = true
}
}
}
}
struct SettingsView: View {
let sortedAccounts = AccountManager.shared.sortedAccounts
@Environment(\.presentationMode) var presentationMode
@ObservedObject private var viewModel = SettingsViewModel()
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..<sortedAccounts.count, content: { i in
NavigationLink(
destination: EmptyView(),
label: {
Text(sortedAccounts[i].nameForDisplay)
})
})
NavigationLink(
destination: EmptyView(),
label: {
Text("Add Account")
})
})
}
var importExport: some View {
Section(header: Text("Feeds"), content: {
NavigationLink(
destination: EmptyView(),
label: {
Text("Import Subscriptions")
})
NavigationLink(
destination: EmptyView(),
label: {
Text("Export Subscriptions")
})
})
}
var timeline: some View {
Section(header: Text("Timeline"), content: {
Toggle("Sort Oldest to Newest", isOn: .constant(true))
Toggle("Group by Feed", isOn: .constant(true))
Toggle("Refresh to Clear Read Articles", isOn: .constant(true))
NavigationLink(
destination: EmptyView(),
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: EmptyView(),
label: {
HStack {
Text("Color Pallete")
Spacer()
Text("Automatic")
.foregroundColor(.secondary)
}
})
})
}
var help: some View {
Section(header: Text("Help"), 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: EmptyView(),
label: {
Text("About NetNewsWire")
})
})
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
}
}

View File

@@ -0,0 +1,112 @@
/*
See LICENSE folder for this samples licensing information.
Abstract:
The iOS implementation of a UIVisualEffectView's blur and vibrancy.
*/
import SwiftUI
// MARK: - VisualEffectBlur
struct VisualEffectBlur<Content: View>: View {
var blurStyle: UIBlurEffect.Style
var vibrancyStyle: UIVibrancyEffectStyle?
var content: Content
init(blurStyle: UIBlurEffect.Style = .systemMaterial, vibrancyStyle: UIVibrancyEffectStyle? = nil, @ViewBuilder content: () -> Content) {
self.blurStyle = blurStyle
self.vibrancyStyle = vibrancyStyle
self.content = content()
}
var body: some View {
Representable(blurStyle: blurStyle, vibrancyStyle: vibrancyStyle, content: ZStack { content })
.accessibility(hidden: Content.self == EmptyView.self)
}
}
// MARK: - Representable
extension VisualEffectBlur {
struct Representable<Content: View>: UIViewRepresentable {
var blurStyle: UIBlurEffect.Style
var vibrancyStyle: UIVibrancyEffectStyle?
var content: Content
func makeUIView(context: Context) -> UIVisualEffectView {
context.coordinator.blurView
}
func updateUIView(_ view: UIVisualEffectView, context: Context) {
context.coordinator.update(content: content, blurStyle: blurStyle, vibrancyStyle: vibrancyStyle)
}
func makeCoordinator() -> Coordinator {
Coordinator(content: content)
}
}
}
// MARK: - Coordinator
extension VisualEffectBlur.Representable {
class Coordinator {
let blurView = UIVisualEffectView()
let vibrancyView = UIVisualEffectView()
let hostingController: UIHostingController<Content>
init(content: Content) {
hostingController = UIHostingController(rootView: content)
hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostingController.view.backgroundColor = nil
blurView.contentView.addSubview(vibrancyView)
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
vibrancyView.contentView.addSubview(hostingController.view)
vibrancyView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
func update(content: Content, blurStyle: UIBlurEffect.Style, vibrancyStyle: UIVibrancyEffectStyle?) {
hostingController.rootView = content
let blurEffect = UIBlurEffect(style: blurStyle)
blurView.effect = blurEffect
if let vibrancyStyle = vibrancyStyle {
vibrancyView.effect = UIVibrancyEffect(blurEffect: blurEffect, style: vibrancyStyle)
} else {
vibrancyView.effect = nil
}
hostingController.view.setNeedsDisplay()
}
}
}
// MARK: - Content-less Initializer
extension VisualEffectBlur where Content == EmptyView {
init(blurStyle: UIBlurEffect.Style = .systemMaterial) {
self.init( blurStyle: blurStyle, vibrancyStyle: nil) {
EmptyView()
}
}
}
// MARK: - Previews
struct VisualEffectBlur_Previews: PreviewProvider {
static var previews: some View {
ZStack {
LinearGradient(
gradient: Gradient(colors: [.red, .blue]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
VisualEffectBlur(blurStyle: .systemUltraThinMaterial, vibrancyStyle: .fill) {
Text("Hello World!")
.frame(width: 200, height: 100)
}
}
.previewLayout(.sizeThatFits)
}
}